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.
Related
I am still baffled finding out how to get a DataGridView updated automatically when changing the content of its DataSource without explicitely triggering DataGridView.Update(). It seems there is no difference at all between DataTable, List, BindingList as (direct) DataSource and as (indirect) DataSource with an additional BindingSource which uses any of the former as DataSource.
The DataGridView I am actually using for this is non-editable and just shows entries which are updated by the corresponding entity code. My last attempt was with a BindingSource that uses a BindingList and manipulating the content of the BindingSource within the code.
I have omitted some methods here, which do not play a role for the basic problem.
Form:
private void FormLog_Load(object sender, EventArgs e) {
...
dgvLog.DataSource = Log.Current.SourceEntries;
...
}
private void ClearLog() {
Log.Current.RemoveAll();
}
public void UpdateDataSource() {
dgvLog.Update();
}
Entity (singleton class):
public class LogEntry {
public int ID { get; set; }
public string DateTime { get; set; }
public string Type { get; set; }
public string Event { get; set; }
public string Details { get; set; }
}
public class Log {
public BindingList<LogEntry> Entries { get; set; }
public BindingSource SourceEntries { get; set; }
public Log() {
Entries = new BindingList<LogEntry>();
SourceEntries = new BindingSource() { DataSource = Entries };
ReadAll();
}
public void Add(string type, string logEvent, string details = "") {
LogEntry entry = MapToDB(new LogEntry() {
Type = type,
Event = logEvent,
Details = details
});
DB.Write(QueryAdd(entry));
SourceEntries.Add(entry);
if (Config.Current.GetForm("Log") != null)
((FormLog)Config.Current.GetForm("Log")).UpdateDataSource();
}
public void ReadAll() {
for (int i = SourceEntries.Count - 1; i >= 0; i--) {
SourceEntries.RemoveAt(i);
}
DataTable dt = DB.Read(QueryReadAll());
if (dt != null) {
foreach (DataRow row in dt.Rows) {
SourceEntries.Add(MapToList(row));
}
}
if (Config.Current.GetForm("Log") != null)
((FormLog)Config.Current.GetForm("Log")).UpdateDataSource();
}
public void RemoveAll() {
DB.Write(QueryRemoveAll());
for (int i = SourceEntries.Count - 1; i >= 0; i--) {
SourceEntries.RemoveAt(i);
}
Add("I", "Log cleared");
}
This works but only when I call UpdateSource() which calls dgvLog.Update() by using a selfwritten FormStack in another singleton class which I would like to avoid. Of course, one could simply call dgvLog.Update() within the form itself but, esp. with this log example, it is obvious that this does not help when updating data from/within another form while the form that displays the DataGridView is still opened in the background.
Also, as there is no difference (between using DataTable or List, etc. and BindingSource or not) I wonder what the benefit/purpose of BindingList and BindingSource are:
Is this the correct approach or am I missing something!?
By the way, I am using .NET v4.5.2.
It seems there is no difference at all between DataTable, List, BindingList as (direct) DataSource and as (indirect) DataSource with an additional BindingSource which uses any of the former as DataSource.
A BindingSource has a few uses
maintains knowledge of position/current row and can thus achieve shared navigation (a dgv and textboxes all bound to the same BS means the dgv can navigate through records and the textboxes update because they always show "current row")
provides sorting and filtering facilities
supports complex binding scenarios where it must help filter a list down to only children of some currently selected parent in a different bindingsource
provides separation for multi different positional browsing of a common DataSource
works but only when I call UpdateSource() which calls dgvLog.Update() by using a selfwritten FormStack in another singleton class which I would like to avoid. Of course, one could simply call dgvLog.Update() within the form itself but, esp. with this log example, it is obvious that this does not help when updating data from/within another form while the form that displays the DataGridView is still opened in the background.
Datagridview.Update() is concerned with repainting areas for the control that need it; it is nothing to do with committing changes to underlying data models. Perhaps you need EndEdit which finishes editing operations on the current row and commits them to the underlying data storage. This also happens when you click a different row in a grid. Bindingsource also have an EndEdit method. Mostly you don't need to call these methods yourself
To share data between forms pass the datatable the data is stored in and bind it through a bindingsource in the second form
Also, as there is no difference (between using DataTable or List, etc. and BindingSource or not) I wonder what the benefit/purpose of BindingList and BindingSource are:
DataTable is a collection of DataRow. A DataRow is at it's heart an object array. The end
Binding list is a list of whatever you want, such as your custom class Person. Arguably more useful ultimately, but it's comparing apple's and oranges. If instead you open up the DataSet Designer then you can specify tables that have named typed column. In that regard it's not a huge amount different from writing your own classes (it writes a large amount of good code on a short time though. I use them as data models sometimes
I am building a custom component, and it has a DataTable as one of the properties.
All is working well, except that I cannot get the designer to serialize the columns from the DataTable property.
I tried the solution in this answer but it does not solves my problem.
In other words, when I drop this custom component on a form in the designer, and then do the following steps
fill in the DataBase property
fill in the SelectText property
choose the value true for the property DoRefreshSchema (it will remain false in the designer because it is only used to trigger the code to populate the DataTable's Columns)
then in the designer, in the property window, I can see all the columns of the DataTable filled as I expect.
But after a build or closing/opening the form, they are all gone again...
So what am I doing wrong here.
this is the code from the custom control (stripped to only show relevant code)
[Serializable]
public partial class gttDataTable : Component, ISerializable
{
private DataTable _Table = new DataTable();
private string _SelectText = "";
private bool _DoRefreshSchema = false;
public gttDataTable()
{ }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public DataTable Table
{
get { return _Table; }
set { _Table = value; }
}
[Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
public string SelectText
{
get { return _SelectText; }
set { _SelectText = value; }
}
public gttDataBase DataBase
{
get { return _gttDataBase; }
set { _gttDataBase = value; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool DoRefreshSchema
{
get { return _DoRefreshSchema; }
set
{
_DoRefreshSchema = false;
if (value)
{
RefreshSchema();
// After this the DataTable has all columns, they are all present in the proporty window of the designer.
// So I hoped that this code below would tell the designer to serialize the DataTable ,but it does not
this.GetChangeService().OnComponentChanged(this, null, null, null);
}
}
}
public void RefreshSchema()
{
if (DataBase != null && SelectText != "")
{
using (DataTable tempTable = new DataTable())
{
// THE Code below fetches the table's schema from the database and returns them as columns in `tempTable`
DataBase.FillDataTable(tempTable, Concat("SET FMTONLY ON", Environment.NewLine, SelectText, Environment.NewLine + "SET FMTONLY OFF"));
}
// This loop then create's a column in the DataTable property for
// every column find in tempTable
foreach (DataColumn col in tempTable.Columns)
{
// I would really like these columns to get serialized in
// designer.cs
_Table.Columns.Add(col.ColumnName, col.DataType);
}
}
}
// I added own serialization code, because I hoped that this would solve my issue, but it does not
private IComponentChangeService GetChangeService()
{
return (IComponentChangeService)GetService(typeof(IComponentChangeService));
}
// Implement this method to serialize data. The method is called
// on serialization.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// Use the AddValue method to specify serialized values.
info.AddValue("DataBase", DataBase, typeof(gttDataBase));
info.AddValue("SelectText", SelectText, typeof(string));
info.AddValue("Table", Table, typeof(DataTable));
}
// The special constructor is used to deserialize values.
public gttDataTable(SerializationInfo info, StreamingContext context)
{
// Reset the property value using the GetValue method.
DataBase = (gttDataBase)info.GetValue("DataBase", typeof(gttDataBase));
SelectText = (string)info.GetValue("SelectText", typeof(string));
Table = (DataTable)info.GetValue("Table", typeof(DataTable));
}
EDIT
I tried adding
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
to the DataTable property as suggested in a comment, but it did not help
EDIT 2
if I add a column manually to the property Table and then change something in one it's properties, this column gets serialized and is persistent.
But all the other columns that are added in code are not.
Is there a way to also serialize them ?
EDIT 3
for clarity, the idea is to serialize the columns of the DataTable property in designtime into the Designer.cs file.
Not at runtime
I'm currently trying to populate a ListView with data imported by the user from a CSV file. As such, I know nothing of the number of columns nor their names at time of compilation, which is why I create a GridView and its columns in code and set it as the ListView's View property.
Normally the ListView supports virtualization when the data is bound, but I can't bind it due to the aforementioned problem. And the fact remains that the view needs virtualization as I expect the user to be importing CSVs with tens of thousands of lines.
As I'm quite new to WPF and MVVM in general, I might be thinking about this problem the wrong way, but I haven't found any practical solution to my problem on here or anywhere. I'm thinking it might be the way I'm representing the data bound to the GridView, which is a custom class with the data being arrays, like this
public class DataSet
{
public double[] Data { get; private set; }
public double[] Targets { get; private set; }
...
}
and binding that in code like this
for (int i = 0; i < numDataSets; i++)
{
gridView.Columns.Add( new GridViewColumn()
{
Header = "N" + (i + 1),
Width = 40d,
DisplayMemberBinding = new Binding("Targets[" + i + "]")
});
}
A second ListView is used to display the Data array variable of the DataSet class, to avoid any confusion.
EDIT
This is the new ViewModel I'm currently using as the data provider of sorts. This model is bound to the DataGrids ItemSource property, and when new data is inbound, a new DataTable is created from it and set to the ViewModels property
public class DataModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged (string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName)); // error thrown here
}
private System.Data.DataTable data;
public System.Data.DataTable Data
{
get { return data; }
set
{
if (data != value)
{
data = value;
OnPropertyChanged("Data");
}
}
}
}
Switch your ListView for a DataGrid and use its AutoGenerateColumns property.
Also, any formatting you need to do to your data collection, do it in your ViewModel before consuming it from the View.
<DataGrid ItemsSource="{Binding Data}"
AutoGenerateColumns="True"
EnableColumnVirtualization="True"
EnableRowVirtualization="True" />
You don't need to define your columns anywhere, they'll be auto-generated based on the columns of your DataTable. And with the last two properties, you activate virtualization so your RAM and processor don't get clogged.
EDIT - Make sure your DataGrid uses its own ScrollViewer. If you put it inside another ScrollViewer or inside a StackPanel, it won't be able to virtualize correctly.
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!
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();