I am creating a reporting component that takes an IEnumerable input and performs some transformations and aggregations and returns a new IEnumerable with a dynamic number of columns. I am using ADO.NET for this because it is easy to create a DataTable with the appropriate columns.
The transformed IEnumerable is sent to a reporting visualization component that uses information stored in Attributes to format the results nicely. It's not necessary to be able to remove or change the attributes once they have been applied.
My question is this:
Is it possible to associate an Attribute with a DataColumn so that the PropertyDescriptor emitted by the ADO.NET DataView includes these Attributes?
Follow-up question:
If this is not possible with ADO.NET are there other libraries that I can use to accomplish this?
Edit: Update for clarity
I would like to be able to do something like this:
DataTable dt = new DataTable();
DataColumn example = dt.Columns.Add("Test",typeof(double));
//This is the functionality I am looking for ideally
example.AddAttribute(new CustomAttribute("Hello"));
public class CustomAttribute : Attribute
{
}
See the ExtendedProperties property on DataColumns. This allows you to add custom data to a datacolumn. You would have to write the code that uses this custom data yourself, however - the formatting (or what ever you intend the data to be used for) isn't automatic.
To answer my own question: It's not possible to inject attributes into a DataColumn and have them appear in the PropertyDescriptor that DataView's implementation of ITypedList emits
Related
I have a DataTable I want to use as a DataSource of a DataGridView, but with a twist: for reasons (below) I need to exclude a column from DataGridView (not just hide it, truly exclude), ideally preventing it from being generated alltogether. Theoretically, I can call Columns.RemoveAt at an appropriate moment (which is the DataBindingComplete event handler - docs), but that's too late for me (for reasons).
An obvious solution is to set AutoGenerateColumns = false and do it manually. Without having looked into the details of this, I fear I'd need to reinvent the wheel in this scenario (to keep the data bindings working etc).
My reasons for this whole esoteric are, there is huge legacy app, originally written in VB6, and there any byte-array column is just ignored by the MS Hierarchical Grid. I'm trying to emulate this behavior in a custom control derived from DataGridView, and most stuff works.
UPDATE/ANSWER
Just set dt.Columns[0].ColumnMapping = MappingType.Hidden; (courtesy of https://stackoverflow.com/a/31282356/5263865)
In modern programming, there is a tendency to separate your data (=Model) from how the data is shown to the operator (=View). An adapter class (=ViewModel) is needed to connect the Model to the View. Abbreviated this gives MVVM. If you are not familiar with this concept of separation, consider to do some background reading.
Your Data is in a DataTable. You didn't mention what kind of items are in the DataTable. To ease the discussion I'll assume that the DataTable contains a sequence of Products.
class Product
{
...
}
You have methods to put Products in the DataTable and to Access them. Something like:
interface IProductRepository
{
IEnumerable<Product> AllProducts {get;}
Product GetProductById(int productId);
void AddProductAtEnd(Product product);
void ReplaceProduct(Product product, int index);
...
}
etc. The exact methods are not important for the answer. What I try to explain is that when using this interface you hide that the Products are stored in a DataTable. This give you the freedom to change where your Products are stored: in a DataBase? A List? or maybe a file, or even the internet.
I use a generic term repository (warehouse) for something where you can store items, and later retrieve them, replace them with other items or remove them from the repository. This can be a DataTable, or a database, or maybe a file, a Dictionary, whatever. The nice thing is that I've hidden that the Products are in a DataTable.
The DataGridView
When accessing the data in a DataGridView, people tend to fiddle directly with the DataGridViewCells and DataGridViewRows.
Well, don't!
Use DataBinding instead.
In almost all forms that have DataGridViews I have the following properties:
BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.DataGridView1.DataSource;
set => this.DataGridView1.DataSource = value;
}
Product CurrentProduct => this.DataGridView1.CurrentRow as Product;
IEnumerable<Product> SelectedProducts => this.DataGridView1.SelectedRows
.Select(row => row.DataboundItem)
.Cast<Product>();
Back to your question
for reasons (below) I need to exclude a column from DataGridView (not just hide it, truly exclude), ideally preventing it from being generated
If I read your question literally: you don't want to generate the DataGridViewCells that are in columns that are excluded.
This does not influence the Product that each row represents, it only influences the display of these Products. For example: even though each Product has an Id, you might want not to Display this Id.
The most easy thing for this is to use visual studios designer for this. Instead of defining the columns with the DataBinder, just add the columns one by one, and use the properties of each column for the name of the column, the name of the property that it has to show, the format that is used to show the value.
Code will look like this:
DataGridView dataGridView1 = new DataGridView();
// Column to show Product.Id
DataGridViewColumn columnProductId = new DataGridViewColumn();
columnProductId.HeaderText = "ID";
columnProductId.DataPropertyName = nameof(Product.Id);
// Column to show Product.Name
DataGridViewColumn columnProductName = new DataGridViewColumn();
columnProductName.HeaderText = "Name";
columnProductName.DataPropertyName = nameof(Product.Name);
// etc. for all columns that you want to show
Note: in DataPropertyName you store the name of the Property that must be shown in this column. I use the keyword nameof, so if later the name of the property changes, this won't be a problem.
Of course, if you want some special formatting, for example for numbers or dates, you need to set the proper properties as well. This can also be done in visual studio designer.
Once that you have defined your columns, add them to the DataGridView.
To Display the Products is a two-liner:
IDataTableProducts ProductRepository {get;} // initialize in constructor
void ShowProducts()
{
IEnumerable<Product> productsToDisplay = this.ProductRepository.AllProducts;
this.DisplayedProducts = new BindingList<Product>(productsToDisplay.ToList());
}
I stumbled upon an answer: https://stackoverflow.com/a/31282356/5263865
Setting the column.ColumnMapping = MappingType.Hidden does exactly what I needed: the column isn't autogenerated anymore.
DataTable data;
data.Columns[0].ColumnMapping = MappingType.Hidden;
This is maybe something I know how to do or have already done it in the past. For some reason I am drawing a blank on how to wrap my head around it. This is more for learning as well as trying to implement something in my app.
I am using a set of third party controls. These controls offer a lot of functionality which is great. However, I want to be able to create a custom object that handle the logic/properties for the datasource of this control.
For example, there is a spreadsheet like object that I am using. You supply the spreadsheet like object some data and it pulls in your data. The problem here is that you need to set the columns, their data types, and other formatting/events as well as some logic to spit the data back to the user.
List<CustomClassWithProperties> dataSource
The custom class has some properties that will be translated to the columns. Like ProductName, Price, SalesDepartment, DatePurchased etc. This can be done by supplying the spreadsheet the columns and their data types each time. I want to be able to create a helper class that you just supply a list, a visible column list, and an editable column list and the data will fill in without any other issues.
Using the above list, I would imagine something similar to this:
DataHelperClass dtHlpr = new DataHelperClass(List<CustomClassWithProperties> data, List<string> visibleColumns, List<string> editableColumns)
This data helper class will take the data input list as the spreadsheet data source. It would then take the visibleColumns list and use that to set the visible rows, same for editableColumns.
Where I am running into a mental block (long week) is when I want to be able to reuse this. Let's say I have a List that has completely different properties. I would want my constructor for the data helper to be able to handle any List I send to it. Looking at whatever code I can get to for the third party controls, it appears that their data source is of type object.
Could someone point me in the right direction? I am thinking it has to do with generics and some interface implementation. I just honestly cannot think of where to start.
You can make the class itself generic:
public class DataHelperClass<T>
{
public DataHelperClass(List<T> data, ...) { ... }
}
DataHelperClass<CustomClassWithProperties> dtHlpr = new DataHelperClass<CustomClassWithProperties>(List<CustomClassWithProperties> data, List<string> visibleColumns, List<string> editableColumns)
You'd then perform your reflection against typeof(T).
I'd also be tempted to use IEnumerable<T> rather than List<T> if possible, but that's a matter of preference, more or less.
This is similar to using a simple List<object>, except that it enforces that all objects in the list inherit from the same type (which might well be object), so you get some more type-checking than you otherwise would.
You mentioned interfaces, I don't see any reason here to include that (from what you've told us, at least), but you can certainly make a generic interface via the same syntax.
I am writing a control which will generate some JavaScript to write a table row to an HTML table and bind back to an IEnumerable at the server side.
Is there any way I can identify:
how many fields each object has
what these properties are called and
their data types?
Yes, and it's called reflection. Or you can simply provide these values declaratively, like when you create GridView and you specify columns for instance...
http://msdn.microsoft.com/en-us/library/ms173183(v=vs.110).aspx
Look at the Type class and the PropertyInfo class.
using c#, vs2008 winforms
I have a datagridview that i am programtically binding to a binding source, that is bound to a dataset with 1 table.
After filling the dataset using the sqlAdaptor, i want to add a new column to the dataset, and in the new column populate it with a result derived from a call to a custom method in the form.
Code Eg is bellow but i get a "Expression contains undefined function call this.Test()
Is it allowed to call methods as such in the expression of the column
thanks in advance for any help
cheers
this.dsProdOrdLst1.ProdOrder.Columns.Add("sOrderType", typeof(string),
"this.Test(order_type)"); // order_type is another column in the dataset
elsewhere in the form class is the method
public int Test(int orderType)
{
return 10; //return a test value
}
AFAIK, these expressions (known as ADO.NET expressions) have no way of calling any kind of user defined functions. It should however by trivial to manually populate the new column:
this.dsProdOrdLst1.ProdOrder.Columns.Add("sOrderType", typeof(string));
foreach (DataRow row in dsProdOrdLst1.ProdOrder.Rows)
{
row["sOrderType"] = Test((int) row["order_type"]);
}
Alternatively, you might be able to implement your entire function in an ADO.NET expression, but you really only want to do this for very simple functions, as otherwise it will be a mess:
IIF(order_type = 0, 'Mail', IIF(order_type = 1, 'Phone', 'Online'))
The documentation of DataColumn.Expression has a list of supported functions (scroll pretty far down to find it).
This is a C# Winform question. I have a DataGridView which is bounded to a DataTable. I construct the DataTable myself, which several DataColumn instances. When the DataTable is bound to the DataGridView, by default, every column is sortable by clicking the headers of the DataGridView.
But the sorting behavior is something "by default". It seems that it is sorted by string. This is true even if I put this as my code:
DataColumn dc = new DataColumn("MyObjectColumn", typeof(MyObject));
And MyObject has overriden ToString() and has implemented the IComparable interface. That means even if I have told the DataTable how to sort the special column with the implementation of IComparable interface, DataGridView still doesn't do it the way I expect.
So how can I let DataTable sort data in the way I want?
Thanks for the answers.
I would recommend using the DefaultView of the DataTable. It has some built in sorting features that are little more extendable. The easiest is RowFilter, but I'm not sure if this will be what you're looking for if your data types are overridden as .ToString() at the table level.
EDIT: added code snippet
A custom method like this that maybe even overrides or is called during the sort event of your DataGridView might be able to sort a DataView before the binding actually occurs. But as I understand it, the IComparable never gets called unless you specify it to be called.
protected void SortGrid()
{
System.Data.DataView dv = myDataTable.DefaultView;
myOjbect comparer = new MyObject();
// Comparer specifics go here. Sort order, column/fieldname etc
// or any custom properties used in sorting
dv.Sort(comparer)
dgMyGrid.DataSource = dv
dgMyGrid.DataBind()
}
I had to deal with this today. I've implemented a natural sort (hat tip: Natural Sort Order in C#) and had to sort a DataTable. This is more of a "low fi" method, but it did the trick for the small dataset I'm working with.
I'm creating a key/value value relationship between my sorting column, and the DataRow itself, and popping it into a SortedList constructed with an IComparer.
DataTable myDataTable = {all my data...}
SortedList myDataNaturallySorted = new SortedList(new NaturalComparer());
foreach (DataRow dataRow in myDataTable.AsEnumerable())
myDataNaturallySorted.Add(dataRow["columWithKeyName"].ToString(), dataRow);
Then I moved forward using the sorted list as a data source for my repeater.