Using custom forms for editing DataRows - c#

I have a DataSet which I want to visualize and work with. I use DataGridView components to display its tables, but when it comes to adding new rows/editing existing rows, I don't want to use DataGridView's functionality: I want to show a custom form instead, with TextBoxes, and ComboBoxes, and whatnot, and OK/Cancel buttons.
There is a table with Name (string), Forme (string), Image (byte[]), Attr1 (string), Attr2 (string), IsSpecial (bool) columns. Forme and Attr2 are nullable, the others are not, and Image should contain a picture in it. Also, Attr1 and Attr2 must be looked up from another table (which has ID (long), Name (string) columns).
So I create a new form, drop two textboxes, one picturebox, two combobxes, and a checkbox on it. I already have a problem: how to represent DBNull in a textbox/combobox?
Next, the binding itself. I reckon I have to do something like this:
class MainForm
{
// ...
private void btAdd_Click(object sender, EventArgs e)
{
using (EditFrom editForm = new EditForm())
{
// I have to create a new row, right?
var newRow = theTable.newStrongTypedRow();
if (editForm.ShowData(binding) == DialogResult.OK)
theTable.addStrongTypedRow(newRow);
}
}
private void btEdit_Click(object sender, EventArgs e)
{
using (EditFrom editForm = new EditForm())
{
if (editForm.ShowData(binding) == DialogResult.OK)
binding.EndEdit();
else
binding.CancelEdit();
}
}
}
class EditForm
{
// ...
public DialogResult ShowData(SomeBinding binding)
{
// tie the controls to the datarow which is being edited right now
BindAllControls(binding);
// let the user input the data
return ShowDialog();
}
}
But what do I use for "SomeBinding"? And what sould be inside BindAllControls()? Or maybe I have this all upside down and it's not how I am supposed to represent/edit data? Maybe someone could suggest a book about this topic?

I would think you would want to use a BindingSource to handle all your bindings.
You can configure the controls to bind to the BindingSource and it should handle all of the translation between the controls and the DataSet.
Bindings can be set like this:
this.nameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.testDataBindingSource, "Name", true));
You could pass in your BindingSource as "SomeBinding".

Related

C# DataGridView and Input Form

I have a DataGridView binded with data from a database. I need to create a Form, which has input fields for data from a single grid row.
The Form has 30+ input controls - TextBoxes, Checkboxes and NumericUpDowns.
For now I went with this approach:
Retrieve current row from the DataGridView and load values from the cells to class instance
Pass the instance to the form and manually fill the input controls
Update the database from the form, update the DataGridView
I want to improve some things:
Is there any way to quickly fill all input controls from a class instance?
Is there any way to determine which input controls have changed their values besides manually subscribing every control on an event handler?
Is there any way to improve this whole thing, e.g. do something more efficiently?
If you are already passing in a DataRow, then you could instead pass in the DataTable and something that identifies the row in that table. And maybe optionally an adapter, if you want to commit the changes immediately on form exit. Then you can create a DataView of that table. And bind each edit control to a field in that view. Something like this:
public partial class EditForm : Form
{
DataRow row = null;
DataView view;
SqlDataAdapter adapter;
public EditForm(SqlDataAdapter adapter, DataTable table, int rowId)
{
InitializeComponent();
this.adapter = adapter;
view = table.DefaultView;
view.RowFilter = $"ID = {rowId}";
if (view.Count == 0) throw new Exception("no such row");
DataRowView dvr = view[0];
row = dvr.Row;
datebox.DataBindings.Add(new Binding("Value", view, "DATE"));
stringbox.DataBindings.Add(new Binding("Text", view, "O_STRING"));
this.FormClosing += EditForm_FormClosing;
}
private void EditForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (row.RowState == DataRowState.Modified) adapter.Update(new DataRow[] { row });
}
}
The above assuming that your table has key column named ID and fields DATE and O_STRING.
This will save you the trouble of creating an intermediate custom class instance, based on that row, moving values to and from various objects and automatically sets the RowStatae in the original table.
Re: value changed indicators. Not sure if there is a really elegant way to do that. Firstly, if I had to, I would change the background (or foreground) color rather than font boldness. Setting font to bold would change the width of the content and that is usually quite annoying. Then, I would add handlers to the TextChanged events (or ValueChange events, for controls that are not text-based). You dont need to write custom handlers to each and every edit control - in the event handler, you get the object sender parameter that points to the control object. Then you can get the field name binded to that control with something like this:
private void stringbox_TextChanged(object sender, EventArgs e)
{
Control ctrl = (Control)sender;
string fieldName = ctrl.DataBindings[0].BindingMemberInfo.BindingMember;
if ((string)view[0].Row[fieldName] != ctrl.Text) ctrl.BackColor = Color.Pink;
}
That way you will only need to add a TextChanged handler once (per edit contol class), not one per every edit box you have.

How Can i Convert datagridview datasource to BindingListView (Equin.ApplicationFramework.BindingListView)

I set this DataBindingComplete event to my datagridview. I want every datasource that binding to datagridview can be sortable by clicking on column.
void MakeColumnsSortable_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
DataGridView dataGridView = sender as DataGridView;
foreach (DataGridViewColumn column in dataGridView.Columns)
column.SortMode = DataGridViewColumnSortMode.Automatic;
}
all of my datasource is List and bindingsource doesnot sort when my list is ended by .ToList
Now . how can i convert datagridview.datasource to Equin.ApplicationFramework.BindingListView and set it again to datasource for make any datagridview sortable?
Proper usage of Equin.ApplicationFramework.BindingListView would be as follows
Upon creation of your form:
Create a BindingListView. It will be filled later with the items that you want to display / sort / filter
Create a BindingSource.
Create a DataGridView.
Add the columns to the properties you want to show
The latter three steps can be done in visual studio designer. If you do that, The code will be in InitializeComponents.
Suppose you want to show / sort / filter elements of MyType Your form will be like:
public class MyForm : Form
{
private BindingListView<MyType> MyItems {get; set;}
public MyForm()
{
InitializeComponent();
this.MyItems = new BindingListView<MyType>(this.components);
// components is created in InitializeComponents
this.MyBindingSource.DataSource = this.MyItems;
this.MyDataGridView.DataSource = this.MyBindingSource;
// assigning the DataPropertyNames of the columns can be done in the designer,
// however doing it the following way helps you to detect errors at compile time
// instead of at run time
this.columnPropertyA = nameof(MyType.PropertyA);
this.columnPropertyB = nameof(MyType.PropertyB);
...
}
You can do without the BindingSource, you can assign the BindingListViewdirectly to the DataSource of the DataGridView. Sorting and Filtering will still work. However the BindingSource will help you to access the currently selected item.
private MyType SelectedItem
{
get {return ((ObjectView<MyType>)this.MyBindingSource.Current)?.Object; }
}
private void DisplayItems (IEnumerable<MyType> itemsToDisplay)
{
this.MyItems.DataSource = itemsToDisplay.ToList();
this.MyItems.Refresh(); // this will update the DataGridView
}
private IEnumerable<MyType> DisplayedItems
{
get {return this.MyItems; }
// BindingListview<T> implements IEnumerable<T>
}
This is all. You don't need to create special functions to sort on mouse clicks. The sorting will be done automatically inclusive deciding on the sort order and displaying the correct sorting glyphs. If you want to sort programmatically:
// sort columnPropertyA in descending order:
this.MyDataGridView.Sort(this.columnPropertyA.ListsortDirection.Descending);
One of the nice things about the BindingListView is the filtering option:
// show only items where PropertyA not null:
this.MyItems.ApplyFilter(myItem => myItem.PropertyA != null);
// remove the filter:
this.MyItems.RemoveFilter();
(I'm not sure if a Refresh() is needed after applying or removing a filter

WinForms Display datagridview object in datagridview

I have several datagridview objects saved in a global dictionary. I want to find a specific datagridview object and display it in a datagridview. Everything seems to work exept for the actual display.
Here is the code that is suppost to display
if (seznamPodatkov.ContainsKey(tn.Text))
{
dataGridView1.DataSource = seznamPodatkov[tn.Text];
}
Maybe I'm supposed to use the databind, but I don't know how to use it with many datagrid object, from which I get the right one.
Edit:
This code below saves datagridview objects, which should be later restored and displayed. The datagridview changes depending on a treeview, if a node with childs is clicked, then first is checked if a gridview from this parent node already exists. If it does, it should display that datagrid from dictionary, if not, it makes a new one and saves it to dictionary.
private Dictionary<string, DataGridView> seznamPodatkov;
if (tree1.SelectedNode == null) return;
if(tree1.SelectedNode.GetNodeCount(false) > 0)
{
if (seznamPodatkov.ContainsKey(tree1.SelectedNode.Text))
{
seznamPodatkov[tree1.SelectedNode.Text] = dataGridView1;
}
else seznamPodatkov.Add(tree1.SelectedNode.Text, dataGridView1);
}
private void tree1_AfterSelect(object sender, TreeViewEventArgs e)
{
TreeNode tn = tree1.SelectedNode;
if (tn == null) return;
if (tn.GetNodeCount(false) > 0)
{
dataGridView1.Columns.Clear();
if (seznamPodatkov.ContainsKey(tn.Text))
{
dataGridView1.DataSource = seznamPodatkov[tn.Text];
}
else
{
dataFill(tn);
}
dataGridView1.Show();
}
else dataGridView1.Hide();
}
Edit:
I made some slight progress. I tried it like this:
DataGridView dgv = new DataGridView();
dgv.Columns.Add("something", "something");
dgv.Columns.Add("something", "something");
dgv.Columns.Add("something", "something");
dataGridView1 = dgv;
datagridView1 seems to get the dvg columns, as I checked it with debugger. I have no idea if this is even correct or not, because the dataGridView1 still doesn't display the table in forms.
I handle things differently when I have such a need. I use a custom TabControl that supports hiding the tabs, and couple that with a TreeView whose selection event controls which tab page is displayed.
This provides the illusion of one view that switches with the tree selection, while in reality you would have multiple, separate controls on the form. This would allow you to do away with the Dictionary and simply bind each DataGridView directly to its data source.
I figured it out. First I thought that it's that simple: dgv = dgv1 but it's not.
Here is a test code that worked perferctly:
Dictionary<string, DataGridView> dic;
public Form1()
{
InitializeComponent();
dataGridView2.Columns.Add("nekaj", "nekaj");
dataGridView2.Columns.Add("nekaj", "nekaj");
dataGridView2.Columns.Add("nekaj", "nekaj");
dataGridView2.Rows.Add("1", "2", "3");
dataGridView2.Rows.Add("1", "2", "3");
dataGridView2.Rows.Add("1", "2", "3");
dic = new Dictionary<string, DataGridView>();
dic.Add("test", dataGridView2);
}
private void button1_Click(object sender, EventArgs e)
{
foreach(DataGridViewColumn columns in dic["test"].Columns)
{
dataGridView1.Columns.Add(columns.Name, columns.HeaderText);
}
foreach(DataGridViewRow rows in dic["test"].Rows)
{
ArrayList list = new ArrayList();
foreach(DataGridViewCell cell in rows.Cells)
{
list.Add(cell.Value);
}
dataGridView1.Rows.Add(list.ToArray());
}

how to pass datagridview value to another form when the datagridviewbutton cell is clicked

I've been working on my datagridview properties and wondering how can I pass the value of my selected data row to another form.
private void dgvRptView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
var senderGrid = (DataGridView)sender;
if (senderGrid.Columns[e.ColumnIndex] is DataGridViewButtonColumn &&
e.RowIndex >= 0)
{
Form Update = new frmUpdateSvcRep();
Update.Show();
}
}
apparently, I was only able to add button inside the datagridview and added an event when I clicked the button, it'll show a form. However. I've been trying to pass the values I selected to textbox in another from but to no avail. Can someone please help me figure out how to pass the value where I click my button? here's my image caption.
here's my another form looks like when I click the edit button inside the Datagridview.
I'm really out of my depth for now.. I'm opt on creating constructors but I don't know how to implement it in this Scenario. Thanks in Advance
There are several ways to accomplish passing data between Forms. One way, as you mentioned, is to pass via the constructor when you instantiate the Form:
private void dgvRptView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (dgvRptView.Columns[e.ColumnIndex] is DataGridViewButtonColumn &&
e.RowIndex >= 0)
if (dgvRptView.CurrentRow != null)
{
var row = dgvRptView.CurrentRow.Cells;
DateTime age = Convert.ToDateTime(row["MyColumn"].Value);
string name = Convert.ToString(row["MyName"].Value);
Form Update = new frmUpdateSvcRep(age, name);
Update.Show();
}
}
Update the constructor of your other Form to accept those parameters:
public class Update : Form
{
public Update(DateTime age, string name)
{
// do whatever you want with the parameters
}
...
}
It might be tempting to just pass the entire dgvRptView.CurrentRow object, but I'd advise against that. Your other Form then has to know about the columns in the DataGridView so it can access the values, which is not something it should be concerned about and could lead to runtime bugs when the column names change.

getting datagridview row data to textboxes on another form

i have a datagridview that show my data in columns.
what i'm trying to accomplish is that after choosing a row and pressing edit button a new form will open and split the row for the right text boxes to update the data.
the datagridview row shows different types of data: name,email,date,etc...
any idea?
Thanks in advance!
This site explains how to send data between forms, it would be as simple as selecting the right cell in the datagrid, sending that info off to the right textbox, for all of them. then sending them back. Data between forms
The basics are to create a method that can be use to get the value,
public string getTextBoxValue()
{
return TextBox.Text;
}
then you can just call the method to pass the data between the forms,
this.Text = myForm2.getTextBoxValue();
however you will be sending the values of the cells, and will be making a textbox.text equal to the return of the method
this is a basic example of the theory, giev it a try yourself to make it work for what you want it to do, if you just cant do it come back and ask for help and ill edit with the code, but only after youve tried yourself first
You can create a class, say MyDataCollection, with properties corresponding to your DataGridView columns. When you press the Edit button, create a new instance of this class, fill it with the necessary data and pass it as parameter to the EditForm's constructor.
public class MyDataCollection
{
public string Name;
public string Email;
// --
}
In your main form:
void btnEdit_Click(object sender, EventArgs e)
{
// Create the MyDataCollection instance and fill it with data from the DataGridView
MyDataCollection myData = new MyDataCollection();
myData.Name = myDataGridView.CurrentRow.Cells["Name"].Value.ToString();
myData.Email = myDataGridView.CurrentRow.Cells["Email"].Value.ToString();
// --
// Send the MyDataCollection instance to the EditForm
formEdit = new formEdit(myData);
formEdit.ShowDialog(this);
}
And the edit form should look like this:
public partial class formEdit : Form
{
// Define a MyDataCollection object to work with in **this** form
MyDataCollection myData;
public formEdit(MyDataCollection mdc)
{
InitializeComponent();
// Get the MyDataCollection instance sent as parameter
myData = mdc;
}
private void formEdit_Load(object sender, EventArgs e)
{
// and use it to show the data
textbox1.Text = myData.Name;
textbox2.Text = myData.Email;
// --
}
}
You can also forget about the MyDataCollection class and pass the entire DataGridViewRow to the formEdit's constructor.

Categories