How can I databind a GridView to a class like the following?
public class GenericEntity
{
private readonly Dictionary<string, object> properties = new Dictionary<string, object>();
public object this[string propertyName]
{
get
{
if (properties.ContainsKey(propertyName))
return properties[propertyName];
else
return null;
}
set
{
if (value == null)
properties.Remove(propertyName);
else
properties[propertyName] = value;
}
}
}
This class may have any number of properties and there is no way to know any of them on compile time, only on runtime, this is because the properties map directly to columns of a result set from the BD.
How can I databind a list of this GenericEntity class to a GridView? I tried the following but I get the exception of 'class does not contain a property with name...'
var newColumn = new BoundField();
newColumn.HeaderText = resultsetDescription.FieldDisplayName;
newColumn.DataField = resultsetDescription.FieldName;
myGridView.Columns.Add(newColumn);
myGridView.DataSource = GetListOfGenericEntities(args);
myGridView.DataBind();
EDIT:
I have implemented the approach mentioned in this SO answer but it still throws the property exception...
If all you need is to bind this generic list to a GridView I would convert this to a DataTable, and then bind the DataTable to the GridView. Have you checked Anonymous Types? You can create a Generic List of Anonymous Types and then bind it to your GridView as well.
Good luck!
Related
I have a class containing details of my customer.
class CustomerData : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get
{ return _Name }
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
// Lots of other properties configured.
}
I also have a list of CustomerData values List<CustomerData> MyData;
I currently databinding an individual CustomerData object to textboxes in the below way which works fine.
this.NameTxtBox.DataBindings.Add("Text", MyCustomer, "Name", false, DataSourceUpdateMode.OnPropertyChanged);
I'm struggling to find a way to bind each object in the list MyData to a ListBox.
I want to have each object in the MyData list in displayed in the ListBox showing the name.
I have tried setting the DataSource equal to the MyData list and setting the DisplayMember to "Name" however when i add items to the MyData list the listbox does not update.
Any ideas on how this is done?
I found that List<T> will not allow updating of a ListBox when the bound list is modified.
In order to get this to work you need to use BindingList<T>
BindingList<CustomerData> MyData = new BindingList<CustomerData>();
MyListBox.DataSource = MyData;
MyListBox.DisplayMember = "Name";
MyData.Add(new CustomerData(){ Name = "Jimmy" } ); //<-- This causes the ListBox to update with the new entry Jimmy.
I have a datagrid bound to an ObservableCollection<MyClass>, and I have another datagrid which has two DataGridTextColumns - Name and Value. The Name column is prepopulated with the names of properties whose values should be displayed in the Value column. MyClass implements INotifyPropertyChanged, so any change in the properties of MyClass objects updates the first datagrid. Now, I would like to display the properties of the currently selected object (SelectedItem) of the first datagrid in the Value column of the second datagrid and see the property changes as they happen, like in the first datagrid. How can I accomplish this?
If you wonder about the reason, only some of the properties are displayed in the original datagrid, so the other one should display almost all of them. Is datagrid even a good choice for displaying properties in 2 columns or should I consider some other control?
This sounds like one convenient solution to a fairly common problem.
The easiest way to do this with two data grids will be for you to use some code behind and reflection. First define a type to display the Name and value of each property:
class PropertyModel {
private readonly string _name = "";
private readonly object _value = null;
public PropertyModel(string name, object value) {
_name = name ?? "";
_value = _value;
}
public string Name {
get { return _name; }
}
public object Value {
get { return _value; }
}
}
Then add an event handler to your code-behind to handle selection changes on your first datagrid:
private void _onDataGrid1SelectionChanged(object sender, SelectedCellsChangedEventArgs e) {
if (e.AddedCells.Count > 0) {
var props = new Collection<PropertyModel>();
var obj = _dataGrid1.SelectedItem;
foreach(var prop in obj.GetType().GetProperties()) {
props.Add(new PropertyModel(prop.Name, prop.GetValue(obj, null)));
}
_dataGrid2.ItemsSource = props;
}
}
Note that the code above is very rough, and will only work if DataGrid1 has SelectionMode set to Single. However this is a good place to start, if you are willing to do it quick and dirty (with an event handler).
Another great solution is to use row details.
This is a pretty good intro tutorial on using row details.
Of course you should also read the msdn article on the subject.
I have this class:
public class CampaignMaps : List<CampaignMap> { };
The CampaignMap object is my of my own and does not inherit anything. It has your standard properties and methods.
I then have a CampaignMapAdapter object which acts as the data to my ObjectDatasource. It has one private property for the data:
[DataObject(true)]
public class CampaignMapAdapter
{
private Database db = new Database(
ConfigurationManager.AppSettings["AppName"],
ConfigurationManager.AppSettings["DBService"],
ConfigurationManager.AppSettings["DBUser"]);
private CampaignMaps _data
{
get
{
CampaignMaps maps = new CampaignMaps();
CampaignsAdapter adapter = new CampaignsAdapter();
db.Init();
db.AddParameter(null, null, ParameterDirection.ReturnValue, OracleType.Cursor);
DataSet ds = db.ExecuteDataSet(PROC_GET_CAMPAIGN_MAPS);
DataTable dt = ds.Tables[0];
foreach (DataRow row in dt.Rows)
{
CampaignMap campaignMap = new CampaignMap();
//populate the campaignMap object...
maps.Add(campaignMap);
}
return maps;
}
set
{
_data = value;
}
}
[DataObjectMethod(DataObjectMethodType.Select, true)]
public CampaignMaps GetFiltered(bool hasErrors)
{
var selectQuery = from c in _data where c.HasError == hasErrors select c;
_data = selectQuery;
}
}
_data = selectQuery; is throwing the error:
Cannot implicitly convert type
'System.Collections.Generic.IEnumerable' to
'CampaignMaps'. An explicit conversion exists (are you missing a
cast?)
I suppose in plain English I want _data to always contain ALL my data elements, then calling a particular select should whittle them down as desired. How do I go about doing this?
Thanks in advance.
Well, you could do:
CampaignMaps maps = new CampaignMaps();
maps.AddRange(selectQuery);
_data = maps;
That would get you the right data in the right types.
However, I would strongly consider not deriving from List<T> to start with - it's almost never a good idea; prefer composition over inheritance unless you're really specializing the behaviour.
I'd also say that mutating the current object in a method called GetFiltered violates the principle of least surprise. I would either change it to make it clearer that it's a mutation (e.g. "FilterByErrors") and give it a void return type, or just make it return the filtered list without mutating the current object.
Why are you doing _data = selectQuery;?
I would think your intention in the GetFiltered method would be something like:
return new CampaignMaps(selectQuery);
However, like Adam said, I would seriously consider why you are using a CampaignMaps class.
If you want the public interface to only allow filtering by "HasErrors", make the GetFiltered method look like this:
public IEnumerable<CampaignMap> GetFiltered(bool hasErrors)
{
return _data.Where(c => c.HasError == hasErrors);
}
I don't see any reason why you'd want to have this GetFiltered method in the first place. Why not expose the _data property as a get-only IEnumerable<CampaignMap> and then allow other objects to run Linq queries on it directly? Like this:
public IEnumerable<CampaignMap> Data
{
get { return _data; }
}
Then somewhere else I can just write Data.Where(c => c.HasError == whatever).
Environment: Visual Studio 2010, .NET 4.0, WinForms
I have a DataSet that implements INotifyPropertyChanged, and have created a bool property on the DataSet. I am trying to bind a CheckBox.Checked property to that bool property. When I try to do it in the designer, I see the DataSet and the tables in the DataSet , but not the property. I attempted to do it manually, but receive the error that the property is not found. The only thing different I see that I'm doing is the property on the form is a superclass of the DataSet that is being instantiated, but I don't even see how that would affect anything. A code snippet is below.
Derived Class Definition
public class DerivedDataSetClass: SuperDataSetClass, INotifyPropertyChanged
{
private bool _mainFile = false;
public bool MainFile
{
get { return this._mainFile; }
set {
this._mainFile = value;
this.NotifyPropertyChanged("MainFile");
}
}
}
Property Definition
private SuperDataSetClass _dataSet;
public DerivedDataSetClass DataSet
{
get { return (DerivedDataSetClass)_dataSet;
}
Ctor
this._DataSet = new DerivedDataSetClass (this);
this.mainFileBindingSource = new BindingSource();
this.mainFileBindingSource.DataSource = typeof(DerivedDataSetClass);
this.mainFileBindingSource.DataMember = "MainFile";
var binding = new Binding("Checked", this.mainFileBindingSource, "MainFile");
this.chkMainFile.DataBindings.Add(binding);
Thoughts?
The problems comes directly from the way you want to use your DerivedDataSetClass. Since it's DataSet, any binding done will use its default DataViewManager, which "pushes" binding further to Tables binding.
When you bind to your DerivedDataSet MainFile property, what is being done under the hood is an attempt to bind to a table named MainFile within your dataset tables. Of course this fails, unless you really have such table in the dataset. For the same reason, you can't bind to any other property of base DataSet - eg. Locale or HasErrors - it also checks whether such tables exist, not properties.
What are the solutions to this problem? You can try implementing different DataViewManager - however I wasn't able to find reliable resources on that topic.
What I suggest is to create simple wrapper class for your MainFile property and associated DerivedDataSetClass, like this:
public class DerivedDataSetWrapper : INotifyPropertyChanged
{
private bool _mainFile;
public DerivedDataSetWrapper(DerivedDataSetClass dataSet)
{
this.DataSet = dataSet;
}
// I assume no notification will be needed upon DataSet change;
// hence auto-property here
public DerivedDataSetClass DataSet { get; private set; }
public bool MainFile
{
get { return this._mainFile; }
set
{
this._mainFile = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("MainFile"));
}
}
}
Now you can bind to both dataset inner content (tables) as well as MainFile on your wrapper class.
var wrapper = new DerivedDataSetWrapper(this._DataSet);
BindingSource source = new BindingSource { DataSource = wrapper };
// to bind to checkbox we essentially bind to Wrapper.MainFile
checkBox.DataBindings.Add("Checked", source, "MainFile", false,
DataSourceUpdateMode.OnPropertyChanged);
To bind data from tables within dataset, you need to bind to DerivedDataSetWrapper DataSet property, and then navigate through tables names and columns. For example:
textBox.DataBindings.Add("Text", source, "DataSet.Items.Name");
... will bind to table Items and column Name in your original _DataSet.
I have a class that stores a list of dictionary entries. I want bind that to a datasource for gridview from codebehind.
Code for dictionary type of , representing ErrorMessage and failed field.
public partial class FailedFields
{
private Dictionary<string, string> Code_Error = new Dictionary<string, string>();
public void AddFailedField(string field, string message)
{
Code_Error.Add(field, message);
}
public Dictionary<string, string> GetFailedFields()
{
return Code_Error;
}
}
Code for List of Dictionary entries.
public partial class ErrorFieldsList
{
private static List<Order.FailedFields> ErrorList = new List<Slab.FailedFields>();
public void AddErrorField(Order.FailedFields errs)
{
ErrorList.Add(errs);
}
public List<Order.FailedFields> GetErrorMessages()
{
return ErrorList;
}
}
Running in Visual Studio debug mode, i can see the list has the error list, but i cannot get it to display in the gridview. Bellow is one of the many ways (the one that makes most sense) i tried to set the list as a datasource.
ErrorBoxGridView.DataSource = FailedRecords.GetErrorMessages(). ;
ErrorBoxGridView.DataBind();
Any idea where i am going wrong ?
Also, i don't want to specify a datasource in the aspx page because i only want to display this when the error occurs.
If interested why i am doing this to store error messages, have a look at this:link 1
Solved Here Related Question
I will document a complete project when i finish on the wiki.
This can not be done I think. What I'd do is:
Instead of using Dictionary<string, string> define a class that contains two public properties for field and message
Create an object data source for that class (using Visual Studios "Data Sources" window)
Have GetErrorMessages() return List<ErrorClass> instead of Dictionary
Assign that list to the binding source.
EDIT
This is to clarify things according to the latest comments. What you need is one class that contains the information for one error. For example:
public class ErrorInfo
{
public string Field { get { ... } }
public string Message { get { ... } }
}
After that you place a BindingSource on your form and (in code) set its DataSource property to a list of error message classes. For example:
private List<ErrorInfo> errorList = new List<ErrorInfo>();
errorList.Add(new ErrorInfo() { ... });
errorList.Add(new ErrorInfo() { ... });
errorList.Add(new ErrorInfo() { ... });
bindingSource.DataSource = errorList;
The data grid view is bound to the BindingSource. You should see data now. You can manually create columns and set them to the respective property names of your ErrorInfo class as well, but then you'd have to set dataGridView.AutoCreateColumns to false somewhere in your code.
Databind List of Dictionnary into a GridView
List<Dictionary<string,string>> resultSet = SOME List of Dictionaries...
DataGridView.DataSource = resultSet.Select(x => new {
fieldOne = x["key1"], fieldTwo = x["key2"]
}).ToList();
DataGridView.DataBind();
Now u can Bind fieldOne and fieldTwo in the DataGridView element...
Kindly check the Link for the precise ans...
Thanks
.NET provides a handy KeyValuePair<(Of <(TKey, TValue>)>) structure, that can be used in cases like this. That way you don't have to define your own class. HTH.
Or you could bind to the Value & Key properties of each Dictionary item:
ErrorBoxGridView.DataSource = FailedRecords.GetErrorMessages();
ErrorBoxGridView.DataTextField = "Value";
ErrorBoxGridView.DataValueField = "Key";
ErrorBoxGridView.DataBind();