Automatically create an input UI from db fields in winforms? - winforms

I'm trying to create a form in winforms to add records to a db, a new customer for example. I'm working with entity framework.
What I did until today is to create a new empty "Customer" object from the class that the entity framework generated. Then I added this empty object to a list and set the list as the datasource of a datagridview.
That way I automatically had in the grid all the required fields to input to the db.
Everything worked.
But now, the client wants a better design for the UI - something that looks like a contact form in web pages and not a grid row.
How can I make something like that automatically, like I had with the datagridview, creating all the input fields automatically according to the db structure without creating manually labels and textboxes?

Your best bet would be to keep the DataGridView but override the style so that it looks far from a grid and more like what your boss is expecting.
Some suggestions to achieve this:
Remove the lines between each row.
Remove the headers and grid borders.
Add lots of padding to each row and column so each entry is spaced
out. For more advanced stuff you may need to override the Paint
method of some of the controls of the grid.

I ended up iterating the grid and creating textboxes and labels in each iteration.
void Generate_TextBoxes()
{
// top of textboxes
int current_top=150;
int current_left = 1000;
// index used to match between each textbox and the properate column in grid
int my_index = -1;
// iterate the grid and create textbox for each column
foreach (DataGridViewColumn col in dataGridView_add_customer.Columns)
{
my_index++;
// generate textboxes only for visible columns
if (col.Visible == true)
{
// increase the top each time for space between textboxes
current_top += 40;
// create a second column of textboxes (not all of them in 1 long column)
if (my_index == 6) { current_top = 190; current_left = 450; }
TextBox t = new TextBox();
t.Top = current_top;
t.Left = current_left;
t.Width = 170;
t.TextChanged +=new EventHandler(t_TextChanged);
// give an 'id' for each textbox with the corresponding index of the grid
t.Name = my_index.ToString();
Label l = new Label();
l.Text = col.HeaderCell.Value.ToString();
l.Top = current_top;
l.Left = current_left + 190;
this.Controls.Add(l);
this.Controls.Add(t);
}
and the function that binds the textbox to the grid:
void t_TextChanged(object sender, EventArgs e)
{
// create a reference in order to be able to access grid properties such as rows..
TextBox tt = (TextBox)sender;
// access the correct cell in the grid using the Name property you gave to the textbox (it was name=current_index..)
dataGridView_add_customer.Rows[0].Cells[int.Parse(tt.Name)].Value = tt.Text;
}

Related

Perform actions after WPF DataGrid data is completely bound

I have an old Windows Forms application at work which I am converting to WPF. As part of this, there is a button which, when clicked, creates a brand new DataGridView and adds it to the page, binding it to data from a SQL query. This data is bever written back to the database, but a new column is added to the end of the data with a checkbox on it, and when the checkbox is changed the ID from the row is passed into another method along with the state of the checkbox.
In WPF, I have the dynamic grid creation working, and the data binding. Having found I couldn't directly add the column to the DataGrid itself, I've added it to the source DataSet table before binding. This works and whos all the data along with a checkbox for each row. However, I can't get an event to fire when the an individual checkbox is clicked.
I have managed to find some code at http://forums.silverlight.net/t/11547.aspx, which causes the row to commit whenever the checkbox is changed, as my code to pass the ID and bool value currently resides in an EditEnding event. Initially the UpdateSourceTrigger was added to each checkbox by looping over each row in the checkbox column during the "Loaded" event of the DataGrid, but this is now failing, and after some internet searching it seems to be because "Loaded" doesn't guarantee that the data has finished binding, and I'm finding that DataGrid.Items contains more items than there are containers in the grid at the point the event fires. Below is the code I'm trying to use to bind the UpdateTrigger, but at row 32 suddenly the ContainerFromIndex method returns null. There are 69 items in dgvNew.Items at that point.
dgvNew.Loaded += new RoutedEventHandler(delegate(object sender, RoutedEventArgs e)
{
for (var i = 0; i < dgvNew.Columns.Count; i++)
{
DataGridBoundColumn column = dgvNew.Columns[i] as DataGridBoundColumn;
if (column != null && column.Header.ToString() == "HasPermission")
{
for (var j = 0; j < dgvNew.Items.Count; j++)
{
DataGridRow row = (DataGridRow)dgvNew.ItemContainerGenerator.ContainerFromIndex(j);
UpdateSourceTriggerHelper.SetUpdateSourceTrigger(column.GetCellContent(row), true);
}
}
}
});
I've also tried wrapping the for loop up in an event handler for the ItemContainerGenerator.StatusChanged and checking that Status is ItemsGenerated, but that didn't help either.
Can anyone see where I'm going wrong, in code or in understanding?

WPF - Display Grid of Results With Dynamic Columns/Rows

I'm querying an online service (google data feed) which can return results that will have different numbers of columns and rows with each request.
So far I have been unable to get the data grid or grid to work for me. Ideally I want something that works like excel - you can just add rows and set the values for an individual cell
You can create a class e.g. myGridCol that represents the column and create a collection of the columns. Read the google data feed and create the columns. Then you need to add the columns individually e.g. myGridCol[0], myGridCol[1] .. as DataGridColumns in the code behind. You cannot bind directly to a column collection.
You simply bind to a collection for the rows that has a collection for the columns.
In my case I am using a GridView but I have used the same approach with DataGrid
In my case sDocs is an ObservableCollection
sDoc has public List DocFields
The Fields collection is exactly the same in each sDoc because I made sure it was.
If the Fields collection is not the same in each sDoc then it does not like that.
sDocs is the ItemsSource for the GridView
Then in the code behind I add the columns. As I said before you cannot bind directly to a columns collection. Notice you can even bind the Path to a Property (.e.g. DispValueShort). My class for DocField has other Properties and methods. DocField is actually an Abstract Class with and Abstract Property DispValueShort. Then I have classes for string, date, and enumeration that implement DocField because edit of a string is different from edit of a date. I even have classes for single value and multi value. This is a stable production application.
Binding
<ListView Grid.Row="1" Grid.Column="0" x:Name="lvSrchResulsGrid"
ItemsSource="{Binding Path=MyGabeLib.Search.SDocs}"
Code behind
sDocBaseResultDocsFieldsIndex = 0;
foreach (GabeLib.DocField docField in sDocBaseResultDocsFields)
{
// Debug.WriteLine(" sDocBaseResultDocsFields DispName = " + docField.FieldDef.DispName);
if (fd.FieldDef == docField.FieldDefApplied.FieldDef)
{
gvc = new GridViewColumn();
gvch = new GridViewColumnHeader();
gvch.Content = fd.FieldDef.DispName;
gvch.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
if (fd.FieldDef.Sort)
{
gvch.Click += new RoutedEventHandler(SortClick);
gvch.Tag = fd.FieldDef.Name;
}
if (!fd.AppliedDispGrid) gvc.Width = 0; // how to hide
gvc.Header = gvch;
gvBinding = new Binding();
gvBinding.Mode = BindingMode.OneWay;
gvBinding.Path = new PropertyPath("DocFields[" + sDocBaseResultDocsFieldsIndex.ToString() + "].DispValueShort");
template = new DataTemplate();
textblock = new FrameworkElementFactory(typeof(TextBlock));
textblock.SetValue(TextBlock.TextProperty, gvBinding);
textblock.SetValue(TextBlock.TextTrimmingProperty, TextTrimming.WordEllipsis);
// <Setter Property="TextTrimming" Value="WordEllipsis" />
template.VisualTree = new FrameworkElementFactory(typeof(Grid));
template.VisualTree.AppendChild(textblock);
gvc.CellTemplate = template;
gvSearchResults.Columns.Add(gvc);
break;
}
sDocBaseResultDocsFieldsIndex++;
}

Datagrid very low performance, even with UI Virtualization

I'm currently using a DataGrid.
about 24 columns are created dynamically in C#.
There's always about 300 entries in my DataGrid (and since one entry represent a "title", I can't create paging systems, cause I have to get all the data in the same page).
It works well, but if I use DataGridTemplateColumns (because I need a styled column header which have a separator and 2 titles, as I need 2 sub columns on each column) and cell templates (still because I need these 2 sub columns), which has a double-binding (one binding for each sub column), when I load the Grid, it's just unusable...
I tried ALL types of virtualization (StackPanel, RowVirtualization, ColumnVirtualization with all different types of value combinations).
The "best" performance I could get is with the RowVirtualization and ColumnVirtualization set to True.
It's now "usable", but still very slow when I do horizontal scrolling (even with a little graphic bug since I use a FrozenColumn...)
I even tried using my own ListView / GridView, and after working on it for hours (in order to reproduced the frozen column, etc...) There's still the same "issue".
It's not possible to use Data Virtualization (since there's "only" 24 columns with 285 rows, it will not user friendly at all).
Thanks !
EDIT 1 : Here is the code generating the columns
ColumnCollection = new ObservableCollection<DataGridColumn>();
DataGridTemplateColumn firstDtc_l = new DataGridTemplateColumn();
firstDtc_l.Header = "Titles";
FrameworkElementFactory spFactory_l = new FrameworkElementFactory(typeof(Grid));
ColumnCollection.Add(firstDtc_l);
int i = 0;
foreach (string s in DynamicColumns)
{
DataGridTemplateColumn dtc_l = new DataGridTemplateColumn();
Binding bindColor = new Binding();
bindColor.Converter = new ChangedColorConverter();
bindColor.ConverterParameter = "Column" + i;
//DataTemplate
DataTemplate dt_l = new DataTemplate("MyObject");
spFactory_l = new FrameworkElementFactory(typeof(Grid));
spFactory_l.Name = "CellTemplate";
FrameworkElementFactory columnDefinition1 = new FrameworkElementFactory(typeof(ColumnDefinition));
FrameworkElementFactory columnDefinition2 = new FrameworkElementFactory(typeof(ColumnDefinition));
FrameworkElementFactory border1 = new FrameworkElementFactory(typeof(Border));
border1.SetValue(Grid.ColumnProperty, 0);
border1.SetValue(Border.BorderBrushProperty, Brushes.Gray);
border1.SetValue(Border.BorderThicknessProperty, new Thickness(0,0,0,0));
FrameworkElementFactory border2 = new FrameworkElementFactory(typeof(Border));
border2.SetValue(Grid.ColumnProperty, 1);
border2.SetValue(Border.BorderBrushProperty, Brushes.Gray);
border2.SetValue(Border.BorderThicknessProperty, new Thickness(1, 0, 0, 0));
FrameworkElementFactory textBlock1 = new FrameworkElementFactory(typeof(TextBlock));
textBlock1.SetValue(Grid.ColumnProperty, 0);
textBlock1.SetValue(TextBlock.ForegroundProperty, bindColor);
Binding firstBind = new Binding("MyObject[Column"+i+"].FirstBinding");
textBlock1.SetValue(TextBlock.TextProperty, localBind);
FrameworkElementFactory textBlock2 = new FrameworkElementFactory(typeof(TextBlock));
Binding secongBind = new Binding("MyObject[Column" + i + "].SecondBinding");
textBlock2.SetValue(Grid.ColumnProperty, 0);
textBlock2.SetValue(TextBlock.TextProperty, firstBind)
textBlock2.SetValue(TextBlock.ForegroundProperty, secongBind);
border1.AppendChild(textBlock1);
border2.AppendChild(textBlock2);
spFactory_l.AppendChild(columnDefinition1);
spFactory_l.AppendChild(columnDefinition2);
spFactory_l.AppendChild(border1);
spFactory_l.AppendChild(border2);
dt_l.VisualTree = spFactory_l;
dtc_l.Width = DataGridLength.Auto;
dtc_l.CellTemplate = dt_l;
dtc_l.Header = s;
ColumnCollection.Add(dtc_l);
i++;
}
The DataGrid is bound to a Collection of "TheObject".
TheObject class has a public Dictionary<string, MyCell> MyObject { get; set; }
MyCell class has FirstBinding and SecondBinding properties (string).
I had a similar problem with the DataGrid in which it took literally seconds to refresh after a window resize, column sort, etc. and locked up the window UI while it was doing so (1000 rows, 5 columns).
It came down to an issue (bug?) with the WPF sizing calculations. I had it in a grid with the RowDefinition Height="Auto" which was causing the rendering system to try and recalculate the size of the DataGrid at runtime by measuring the size of each and every column and row, presumably by filling the whole grid (as I understand it). It is supposed to handle this intelligently somehow but in this case it was not.
A quick check to see if this is a related problem is to set the Height and Width properties of the DataGrid to a fixed size for the duration of the test, and try running again. If your performance is restored, a permanent fix may be among these options:
Change the sizes of the containing elements to be relative (*) or
fixed values
Set MaxHeight and MaxWidth of the DataGrid to a fixed value larger
than it could get in normal use
Try another container type with different resizing strategy (Grid, DockPanel, etc)

Can I programmatically add a row to a WPF datagrid?

I just want to add a new row, I have my datasource in objects in which I need to do some processing. i need something like below for wpf datagrid...
DataRow row = dataTable.NewRow();
foreach (NavItem item in record.Items)
{
row[item.FieldNo.ToString()] = item.RecordValue;
}
dataTable.Rows.Add(row);
You should be using an ObservableCollection<NavItem> as the datagrid source. Then simply adding a new element to your collection will add it to the datagrid.
See this MSDN article.
I do not know if it is the right solution, but I came up to something like this, in desperation:
foreach (NavField field in this.Fields)
{
DataGridTextColumn column = new DataGridTextColumn();
column.Header = field.FieldNo.ToString();
//Some other logic
// Hide non active and hidden fields
if (!field.Active || !field.Show)
column.Visibility = System.Windows.Visibility.Collapsed;
grid.Columns.Add(column);
}
Then I add the datatable as itemssource:
this.dataGridLines.ItemsSource = dataTable.DefaultView;
If I set the datatable directly, it does not care about the columns from the datatable and autogenerate its own columns, don't know why..

WPF DataGrid sort by ComboBox field

I have WPF datagrid with combox column (ID is real value, Desc is displayed value) and when I click on header of that column automatically sorts by real value (ID). I want to sort by displayed value.
My WPF datagrid has 4 columns: IdPerson, DescSchool, IdSchool and School. Column "School" is comboBox with this values:ItemSource = schoolTable.DefaultView, SelectedValueBinding = new Binding("IdSchool"), SelectedValuePath="IDSchool", DisplayMemberPath = "DescSchool"
schoolTable is a table with 2 columns - IDSchool and DescSchool. That table is used just as datasource for combobox.
I tried the solution when I have set SortMemberPath = "DescSchool" and initially, this works - when I click on the header of the combobox column sorting is done by displayed value (because it read value of the other column) and not by real value. But, if I change the value of the combobox, value of the column "DescSchool" is still the same so after that sorting doesn't work anymore properly.
Any idea?
Setting SortMemberPath="Desc" (or what your property is called) on the DataGridComboBoxColumn should do the trick.
fall into similar problem recently.
try something like :
SortMemberPath="School.DescSchool"
Hope it will help you or someone else!
I have also had this problem and had to implement IComparable on the type that is being sorted. So in your case I think it is the School type. Inside IComparable, return this:
return this.Desc.CompareTo((obj as School).Desc);
This is the only way I was able to get this to work, and judging by the lack of responses, not many people know a better way...sorry.
Also, this will only work if you have access to the types. If this is a datatable or something like that (as opposed to Entity Framework for example) this solution will not work.
I had similar problem to solve with WinForm and its DataGridView displaying relational data in a combobox column in the grid. Wanted to sort the column but the sorting would sort by the ValueMember (an int ID field) and not the DisplayMember (text field being displayed).
I was using a typed dataset and put its typed table into a DataView and use that DataView as the Datasource for a BindingSource which was then the Datasource for the grid. :-)
After reading several posts on the web, the solution that worked for me was the following:
In the code-behind of the Typed dataset, I added some custom properties to the Typed Row of my main typed table.
Example:
public string ProvinceAtRow
{
get
{
string result = string.Empty;
if (!this.CustomerInfoRow.IsProvinceDescriptionNull())
{
result = this.CustomerInfoRow.ProvinceDescription;
}
return result;
}
}
Next in the code-behind of the WinForm that has an instance of my typed Dataset, I added a DataColumn to the typed table.
Example (code in the Load event of the winForm):
this.DSGrowth.LocalGrowthFactor.Columns.Add(
new DataColumn("Province", typeof(string)));
Next when have data in the dataset, must fill in the column(s) that were added.
Example:
//if we have data then have to fill in the colums we added to the TDS
if (this.DSGrowth.LocalGrowthFactor.Rows.Count > 0)
{
//fill columns so that can display on datagrid as columns that can be sorted
// ref http://www.daniweb.com/forums/thread283915.html
Array.ForEach<dsLocalGrowthFactor.LocalGrowthFactorRow>( this.DSGrowth.LocalGrowthFactor.Rows.OfType<dsLocalGrowthFactor.LocalGrowthFactorRow>().ToArray(),
row =>
{
row["Province"] = row.ProvinceAtRow;
}
);
//accept changes on TDS so not to be prompted to save changes
this.DSGrowth.AcceptChanges();
}
//set default sort
DataView dvFactors = new DataView(this.DSGrowth.LocalGrowthFactor);
dvFactors.Sort = "GrowthFactor DESC";
this.BSLocalGrowth.DataSource = dvFactors;
this.DgvGrowth.DataSource = this.BSLocalGrowth;
if (this.DgvGrowth.Rows.Count > 0)
{
//select first row
this.BSLocalGrowth.MoveFirst();
this.DgvGrowth.Rows[0].Selected = true;
}
No more ComboBox columns and sorting works!
Hope this helps all out there who are in similar situation.

Resources