I'm working on a small program as part of my A Level Computing course that is designed to track orders. It is written in C# using the Windows Forms.
I am having an issue where I enter all the information for a new order and then press OK and it should update the ListView with the information. I have my ListView in Detail view with 4 columns but nothing ever gets added to the ListView. The section of code that should add the items to the ListView is being executed and is not throwing any errors or causing the program to crash but nothing is being added. Its weird because I am using the exact same method that I used in my little prototype mock up but for some reason now it is not working.
All the things I've found on here or on the internet seem to suggest its an issue with the View mode of the ListView and I've tried modifying this property to no avail.
Any ideas why this section of code is refusing to add anything to the ListView?
//Create an array to store the data to be added to the listbox
string[] orderDetails = { Convert.ToString(id + 1), rNameBox.Text, dateBox.Value.ToString(), orderBox.Text };
//DEBUGGING
Console.WriteLine(orderDetails[0]);
Console.WriteLine(orderDetails[1]);
Console.WriteLine(orderDetails[2]);
Console.WriteLine(orderDetails[3]);
//END DEBUGGING
//Add the order info to the ListView item on the main form
var listViewItem = new ListViewItem(orderDetails);
ths.listView1.Items.Add(listViewItem);
If you need any more information just say. Apologies if this is in the wrong format or something this is my first time here.
Your problem is that your ListViewItem contains a string array and it has no useful way of displaying it.
What you should be doing (there are a number of ways of doing this, but here's one) is creating a class, OrderDetail, with an Id, a Name, a Date, and so on. Give it a ToString() method (public override string ToString()) which returns what you want to display, e.g.:
public override string ToString()
{
return this.Name;
}
Create an instance of OrderDetail and set its properties. Create ListViewItem giving it the OrderDetail instance and add to the ListView. Repeat for as many OrderDetail instances you want.
Cheers -
Added: code which works:
int id = 12;
string rNameBoxText = "rName";
DateTime dateBoxValue = DateTime.Now;
string orderBoxText = "order";
string[] orderDetails = { Convert.ToString(id + 1), rNameBoxText, dateBoxValue.ToString(), orderBoxText };
//DEBUGGING
Console.WriteLine(orderDetails[0]);
Console.WriteLine(orderDetails[1]);
Console.WriteLine(orderDetails[2]);
Console.WriteLine(orderDetails[3]);
//END DEBUGGING
this.listView1.Columns.Clear();
this.listView1.Columns.Add("Id");
this.listView1.Columns.Add("rName");
this.listView1.Columns.Add("Date");
this.listView1.Columns.Add("Order");
this.listView1.View = View.Details;
//Add the order info to the ListView item on the main form
var listViewItem = new ListViewItem(orderDetails);
this.listView1.Items.Add(listViewItem);
Related
I have bound a DataGridView to an SQL Server table in a .Net 5.0 WinForms project. Displaying the data works well.
I would like to update editions to the database as soon as I move to another row in the DataGridView. But I have not found a way to do this.
The solution presented here seems not to work with an OleDbDataAdapter. The Update method does not accept a DataRow. Examples in DOCS require a DataSet which I try to avoid. Other examples use a button to save changes.
The data gets loaded like this:
var dataAdapter = new OleDbDataAdapter(sqlQueryString, connString);
var dataTable = new DataTable();
dataAdapter.Fill(dataTable); // fill data table from SQL server
var bindingSource = new BindingSource();
bindingSource.PositionChanged += new System.EventHandler(bindingSource_PositionChanged);
bindingSource.DataSource = dataTable; // connect binding source to data table
dataGridView.DataSource = bindingSource; // connect DataGridView to binding source
For the update I finally have tried this:
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
DataRow dataRow = ((DataRowView)((BindingSource)sender).Current).Row;
if (dataRow.RowState == DataRowState.Modified) // this is successful
{
dataAdapter.Update(dataRow); // compile error
}
}
I get the compile error
Cannot convert from 'System.Data.DataRow' to 'System.Data.DataRow[]'.
Any hint is appreciated.
MVVM
In modern programming, there is the tendency to separate the model from the view. This separation makes it easier to change the way that your data is displayed without having to change your model. You can also change parts of the model without having to change the display. It is easier to reuse the model and to unit test it without having to start a forms program.
In WPF this separation between model and view is almost enforced. When using winforms you have to take care that you do not mix them more than needed.
To keep these two separated, adapter code is needed to glue your model to your view. This adapter code is quite often called the viewmodel. the abbreviation of these three is quite often called MVVM. Consider to familiarize yourself with the ideas of MVVM.
Use a BindingList in your DataSource
If you want to separate your model from your view, you need methods to fetch the data that must be displayed from the database, and data to update items.
I don't know what you will be displaying in your DataGridView, but let's assume it is a sequence of Products, something like this:
class Product
{
public int Id {get; set;}
public string ProductCode {get; set;}
public string Name {get; set;}
public string Description {get; set;}
public decimal Price {get; set;}
...
}
You will have methods to fetch the Products that must be displayed, and to Update one Product, or maybe several Products at a time:
IEnumerable<Product> FetchProductsToDisplay(...)
{
// TODO: fetch the products from the database.
}
void UpdateProduct(Product product) {...}
void UpdateProducts(IEnumerable<Product> products) {...}
Implementation is out of scope of this question. By the way, did you notice, that because I put fetching and updating data in separate procedures, I hid where the Products are saved? It can be in an SQL server, but if you want it could also be a CSV or XML file, or even a dictionary, which could be handy for unit tests.
Besides: you can unit tests these methods without using your forms.
Using the visual studio designer you have added the columns and defined which column should show which Product property. You could also have done this in the constructor using property DataGridViewColumn.DataPropertyName
public MyForm()
{
InitializeComponents();
this.columnProductCode.DataPropertyName = nameof(Product.ProductCode);
this.columnName.DataPropertyName = nameof(Product.Name);
...
}
You don't need to set the DataPropertyName for properties that you won't show anyway.
Now to display the products, it is enough to assign the Products to the DataSource of the DataGridView:
var productsToDisplay = this.FetchProductsToDisplay(...);
this.dataGridView1.DataSource = productsToDisplay.ToList();
This will display the products. However, changes that the operator makes: Add / Remove / Edit rows are not updated. If you need this functionality, then the Products need to put in an object that implements IBindingList, like (surprise!) BindingList<Product>:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
To Initialize the DataGridView:
private void DisplayProducts()
{
this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());
}
Now whenever the operator makes any change to the DataGridView: Add / Remove rows, or change the Displayed values in a row, these changes are reflected in DisplayedProducts.
If for instance the operator clicks Apply Now to indicate he has finished editing the products:
private void OnButtonApplyNow_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Of course you can Add / Remove / Change displayed products programmatically:
void AddProductsToDisplay()
{
Product product = this.DisplayedProducts.AddNew();
this.FillNewProduct(product);
}
Back to your question
Ask yourself: Is it wise to update the database as soon as the position is changed?
If the operator starts typing, then remembers he can copy-paste items, he will stop typing, go to other controls to copy, and then continue editing the cell by pasting. Maybe he goes to other rows to look at information to decide what to put in the cell.
Another scenario: the Descriptions of Product A and Product B need to be exchanged. Think of the operator actions needed for this. When would it be wise to update the database? When are you certain that the operator is content with the new data?
Hence it is not wise to update the database as soon as a row is edited. The operator should explicitly indicate he has finished editing.
private void OnButtonOk_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Further improvements
Once you've separated your data (model) from the way this data is displayed (view), using the DataSource, it is quite easy to access the Product that is displayed in the current row or in the selected rows:
Product CurrentProduct => (Product) this.dataGridView1.CurrentRow?.DataBoundItem;
IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
you can use foreach loop.
private void AddInfo()
{
// flag so we know if there was one dupe
bool updated = false;
// go through every row
foreach (DataGridViewRow row in dgv_Purchase.Rows)
{
// check if there already is a row with the same id
if (row.Cells["Pro_ID"].ToString() == txt_ProID.Text)
{
// update your row
row.Cells["Purchase_Qty"] = txt_Qty.Text;
updated = true;
break; // no need to go any further
}
}
// if not found, so it's a new one
if (!updated)
{
int index = dgv_Purchase.Rows.Add();
dgv_Purchase.Rows[index].Cells["Purchase_Qty"].Value = txt_Qty.Text;
}
}
Finally I've found the 2 missing lines:
private SqlCommandBuilder commandBuilder; // on UserControl level
commandBuilder = new SqlCommandBuilder(dataAdapter); // when loading data
A book has helped me: Michael Schmalz, C# Database Basics, O'Reilly
It is strange that the DOCS reference of SqlDataAdapter doesn't mention the SqlCommandBuilder.
Thanks to everybody who has spent precious time for a New contributor.
I am using C# Windows Forms and a codefirst database (Visual Studio 2013 Ultimate).
Is it possible to display a list inside another list in Windows Forms? (The emphasis is on -displaying- the data).
Please see this project as an example: https://postimg.cc/image/inunj8pxh/
I usually display a list with powerpacks´ datarepeater. For example when an order is placed by a customer, I can display the orderId, customerEmail, customerName etc. of the list of orders.
However, each order includes many different items. So far, I am not able to display any elements of the child-list (items) inside each element the datarepeater, where the parent-list (orders) is shown. The foreign-key of each item is the orderId, and the foreign-key of the order is the list of items (relationship order...items is 1..n).
I found the solution! It took me a while to figure out how to gain control over datarepeater items. Reading across many other forums and tutorials, I gradually worked my way through. Find in the screenshot my complete project:
http://i.stack.imgur.com/jFa7G.png
Any improvements in the code are more than welcome. Since I am quite new to the whole programming world, my code may not be optimized and the use of vocabulary may sometimes be inaccurate.
Here you find the code of my Form1:
namespace list_inside_list
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
protected override void OnLoad(EventArgs e)
{
//First we load the list of Orders to datarepeater1
Program.CompanyContext _context = new Program.CompanyContext();
List<list_inside_list.Program.Order> listOrders = _context.Order.ToList();
program_OrderBindingSource1.DataSource = listOrders;
//I don´t know why, but we need to load the list of items as well, although we never use the listItems variable
List<list_inside_list.Program.Item> listItems = _context.Item.ToList();
/*
* 1. We will loop through all the datarepater1 items (which is fed with listOrders)
* 2. We assign currItem as datarepeater1.CurrentItem in order to "select" the current item at index j,
* although we will never user currItem
* 3. We tell the program that of the current datarepeater item we want use the current Order object
* 4. We go through each of the currentOrder.items and print the itemName
*
*/
DataRepeaterItem currItem = new DataRepeaterItem();
for (int j = 0; j < this.dataRepeater1.ItemCount; j++)
{
this.dataRepeater1.CurrentItemIndex = j;
currItem = dataRepeater1.CurrentItem;
var currentOrder = (list_inside_list.Program.Order)program_OrderBindingSource1.Current;
foreach (var item in currentOrder.items)
{
dataRepeater1.CurrentItem.Controls["richTextBox1"].Text
= dataRepeater1.CurrentItem.Controls["richTextBox1"].Text + item.itemName + "\n";
}
}
}
}
}
I'm not very experienced on c#. I'm working with winforms and I'm looking for a way to create something like a list of elements with this template , something like the autocompletion list of visual studio.
Is it possible to do? Shall I use listbox or listview?
EDIT
Sorry my question wasn't clear I don't want to create an autocomplete but what i want to create is something like this a list of things with an icon next to the text of that thing.
As I understand from your question, you can create custom UserControl or create a Form and put ListBox in it. If you use From be sure that you change border style layout, just set it to none. After creation for use it you should create form and show it where you want like this:
FrmAutoComplete x = new FrmAutoComplete();
x.Show();
you can put this form in ToolTipItem and show it.
Good luck.
THis is a quick and dirty example of using images in your Listview control. Since I don;t have a lot of information about what you plan to do, I tried to keep is simple.
In short, you need to load some images into one of the ImageLists (Large or Small) built into the Listview control and assign them keys so that you can assign them to specific list items as you add them.
The trick to this is determining which image to use for a specific list item (assuming there are different images assigned to different list items depending on some differentiating factor. For this example, I used an arbitrary assignment of "cars" or "trucks," and simply decided that the first five items in the list would be cars, and the last five would be trucks. I then assigned each image appropriately, using the image key as I added each listview item. You can do this for more complex scenarios, and when using the image key, it does not matter what order the items are added.
For this use case, you will want to create or use images with dimensions of 16 x 16 pixels. I went ahead and added two images to my project resource file, then simply accessed them using the project Properties.Resources name space. There are other ways to do this as well, but this is the most convenient for me.
Hope that helps.
public partial class Form1 : Form
{
static string CAR_IMAGE_KEY = "Car";
static string TRUCK_IMAGE_KEY = "Truck";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.SetupListview();
this.LoadListView();
}
private void SetupListview()
{
var imgList = new ImageList();
imgList.Images.Add("Car", Properties.Resources.jpgCarImage);
imgList.Images.Add("Truck", Properties.Resources.jpgTruckImage);
var lv = this.listView1;
lv.View = View.List;
lv.SmallImageList = imgList;
}
private void LoadListView()
{
for(int i = 1; i <= 10; i++)
{
string currentImageKey = CAR_IMAGE_KEY;
if(i > 5) currentImageKey = TRUCK_IMAGE_KEY;
var item = this.listView1.Items.Add("Item" + i.ToString(), currentImageKey);
}
}
in my Win Forms app I create an array of dynamic custom controls inside a loop. These, lets call them 'boxes', are like my basic pieces of information. I also create string arrays in other parts of the code that contain the information of this 'boxes', so that for example string[3] is a variable of box[3] and so does stringa[3], stringb[3], stringc[3]... all the arrays with the same index are related to the box with that index. Hope I make myself clear.
Only 2 of this strings are shown in 2 labels inside each custom control 'box' in the array, but the others are there because I want to make something so that when the user clicks one of these controls the other strings can be shown in another control. Sort of something like "More Information...". All the 'boxes' in the array need to have the same event handler because I create +100.
To put it more into context, each custom control 'box' in the array shows the Symbol and the Price of a stock and I want that when the user clicks on each stock more quote information is shown on another special control which is like a placeholder for "More info".
I am thinking of 2 ways to do it:
If I could "detect" the index of the clicked control (which is the same in the strings related to it), I could just set this to an int j and all I have to do is show all the strings a,b,c... with index j. Unfortunately I cannot find a way to do this, maybe it is not even possible.
The other way I have thought is to create some properties for my custom control which "store" this variables, and in my app instead of assigning strings I would set properties for each control, which I could later retrieve when the control is clicked. I haven't tryed this because I don't know exactly how to do it.
What do you think? Do you know how can I achieve this or do you have a different idea that will work? Please help! Thanks in advance.
It's kind of a broad implementation question since there are countless ways you could implement something like this.
If you are creating two collections, one with the buttons and one with the information, you potentially could just assign each of the buttons 'Tag' properties to point to the corresponding info and assign a generic OnClick event handler that displays the info.. something like:
infoControl.text = ((InfoClass)((Button)Sender.Tag)).pieceOfInformation;
But again there are many ways to do this, and the choice comes down to how you store your information.
For your first method, you could have a property of your custom control that is the index.
public class Box : Control
{
// ...existing code
private int index;
public int Index
{
get
{
return index;
}
set
{
index = value;
}
}
}
OR
For your second method, you could have a property of your custom control that is the additional info string.
public class Box : Control
{
// ...existing code
private string extraInfo;
public string ExtraInfo
{
get
{
return extraInfo;
}
set
{
extraInfo = value;
}
}
}
In either case, you could then access the proper information right in your click handler for the "box".
i don't know about the first way - got to noodle around more, but in the second way you can extended your custom or built-in control: for example:
public class ExtendedLabel: Label
{
public string[] MoreInfo { get; set; }
}
and initialize it
public TestForm()
{
InitializeComponent();
ExtendedLabel label = new ExtendedLabel();
label.MoreInfo = new string[] { "test" };
this.Controls.Add(label);
label.AutoSize = true;
label.Location = new System.Drawing.Point(120, 87);
label.Name = "label1";
label.Size = new System.Drawing.Size(35, 13);
label.TabIndex = 0;
label.Text = label.MoreInfo[0];
}
And later in your event handler you can use the inside information
I've created a control derived from ComboBox, and wish to unit test its behaviour.
However, it appears to be behaving differently in my unit test to how it behaves in the real application.
In the real application, the Combobox.DataSource property and the .Items sync up - in other words when I change the Combobox.DataSource the .Items list immediately and automatically updates to show an item for each element of the DataSource.
In my test, I construct a ComboBox, assign a datasource to it, but the .Items list doesn't get updated at all, remaining at 0 items. Thus, when I try to update the .SelectedIndex to 0 in the test to select the first item, I recieve an ArgumentOutOfRangeException.
Is this because I don't have an Application.Run in my unit test starting an event loop, or is this a bit of a red herring?
EDIT: More detail on the first test:
[SetUp]
public void SetUp()
{
mECB = new EnhancedComboBox();
mECB.FormattingEnabled = true;
mECB.Location = new System.Drawing.Point( 45, 4 );
mECB.Name = "cboFind";
mECB.Size = new System.Drawing.Size( 121, 21 );
mECB.TabIndex = 3;
mECB.AddObserver( this );
mTestItems = new List<TestItem>();
mTestItems.Add( new TestItem() { Value = "Billy" } );
mTestItems.Add( new TestItem() { Value = "Bob" } );
mTestItems.Add( new TestItem() { Value = "Blues" } );
mECB.DataSource = mTestItems;
mECB.Reset();
mObservedValue = null;
}
[Test]
public void Test01_UpdateObserver()
{
mECB.SelectedIndex = 0;
Assert.AreEqual( "Billy", mObservedValue.Value );
}
The test fails on the first line, when trying to set the SelectedIndex to 0. On debugging, this appears to be because when the .DataSource is changed, the .Items collection is not updated to reflect this. However, on debugging the real application, the .Items collection is always updated when the .DataSource changes.
Surely I don't have to actually render the ComboBox in the test, I don't even have any drawing surfaces set up to render on to! Maybe the only answer I need is "How do I make the ComboBox update in the same way as when it is drawn, in a unit test scenario where I don't actually need to draw the box?"
Since you're simply calling the constructor, a lot of functionality of the combobox will not work. For example, the items will be filled when the ComboBox is drawn on screen, on a form. This does not happen when constructing it in a unit test.
Why do you want to write a unit test on that combobox?
Can't you seperate the logic which now is in the custom control? For example put this in a controller, and test that?
Why don't you test on the DataSource property instead of the Items collection?
I'm sure that Application.Run absence cannot affects any control's behavior
I'm having the same problem with a combo box where the items are data bound. My current solution is to create a Form in the test, add the combo box to the Controls collection, and then show the form in my test. Kind of ugly. All my combo box really does is list a bunch of TimeSpan objects, sorted, and with custom formatting of the TimeSpan values. It also has special behavior on keypress events. I tried extracting all the data and logic to a separate class but couldn't figure it out. There probably is a better solution but what I'm doing seems satisfactory.
To make testing easier, I created these classes in my test code:
class TestCombo : DurationComboBox {
public void SimulateKeyUp(Keys keys) { base.OnKeyUp(new KeyEventArgs(keys)); }
public DataView DataView { get { return DataSource as DataView; } }
public IEnumerable<DataRowView> Rows() { return (DataView as IEnumerable).Cast<DataRowView>(); }
public IEnumerable<int> Minutes() { return Rows().Select(row => (int)row["Minutes"]); }
}
class Target {
public TestCombo Combo { get; private set; }
public Form Form { get; private set; }
public Target() {
Combo = new TestCombo();
Form = new Form();
Form.Controls.Add(Combo);
Form.Show();
}
}
Here is a sample test:
[TestMethod()]
public void ConstructorCreatesEmptyList() {
Target t = new Target();
Assert.AreEqual<int>(0, t.Combo.DataView.Count);
Assert.AreEqual<int>(-1, t.Combo.SelectedMinutes);
Assert.IsNull(t.Combo.SelectedItem);
}
This solve some problems if target is ComboBox or any other control:
target.CreateControl();
but I was unable to set SelectedValue it has null value, my test working with two data sources for combo box, one as data source and second is binded to selevted value. With other controls everithing working fine. In the begining I was also creating form in tests, but there is problem when form on created on our build server while tests are executed.
I did a little hack to allow this in my custom derived combobox:
public class EnhancedComboBox : ComboBox
{
[... the implementation]
public void DoRefreshItems()
{
SetItemsCore(DataSource as IList);
}
}
The SetItemsCore function instructs the base combobox to load internal items with the provided list, it's what uses internally after the datasource changes.
This function never gets called when the control is not on a form, because there are lots of checks for CurrencyManagers and BindingContexts that are failing because this components, I believe, are provided by the parent form somehow.
Anyway, in the test, you have to call mECB.DoRefreshItems() just after the mECB.DataSource = mTestItems and everything should be fine if you only depend on the SelectedIndex and the Items property. Any other behavior like databinding is probably still not functional.