Can't get my custom control to serialize from the designer - c#

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

Related

Adding Cache Attached to add Custom Selector to the grid of the Claim Expenses page

I have the following problem:
I want to add a custom selector to the InventoryID field of the EP301000 page grid from Acumatica but when I add a cache Attached to it:
The InventoryID selector changes correctly to my custom selector.
But when you select an item from the lookup the field keep showing its
information blank as if an item is not selected.
If the field data is filled by typing it the same thing happens and the field blanks itself.
So the lookup is showing the correct information but the field doesn't fill when you select the desired record.
Using a regular Selector also blanks the field.
I tried without the any selector, and the same thing happens - in this case, item shows its integer value instead of the CD given that there is no Selector.
This is my extended graph:
public class ExpenseClaimEntrySSGExt : PXGraphExtension<ExpenseClaimEntry>
{
#region Cache Attached
#region InventoryID
//Cache attached use:
//Add Custom Selector
[PXUIField(DisplayName = "Expense Item")]
[SSGCustomExpenseItem(typeof(EPExpenseClaimDetails.contractID))]
protected virtual void EPExpenseClaimDetails_InventoryID_CacheAttached(PXCache Sender)
{
}
#endregion
#endregion
#region CustomSelectors
[PXDBInt]
public class SSGCustomExpenseItemAttribute : PXCustomSelectorAttribute
{
private Type _ContractID;
public SSGCustomExpenseItemAttribute(Type contractID)
: base(typeof(InventoryItem.inventoryID))
{
_ContractID = contractID;
this.SubstituteKey = typeof(InventoryItem.inventoryCD);
this.DescriptionField = typeof(InventoryItem.descr);
}
private string GetSelection()
{
var cache = _Graph.Caches[_BqlTable];
return cache.GetValue(cache.Current, _ContractID.Name)?.ToString(); //Gets the field value by the field name without raising any events.
}
protected virtual IEnumerable GetRecords()
{
string contractString = GetSelection();
int contractID = -1;
contractID = Convert.ToInt32(contractString);
if (contractID != -1)
{
Contract contractRow = PXSelect<Contract,
Where<Contract.contractID, Equal<Required<Contract.contractID>>>>
.Select(this._Graph, contractID);
CSAnswers cSAnswersRow = PXSelect<CSAnswers,
Where<CSAnswers.refNoteID, Equal<Required<CSAnswers.refNoteID>>,
And<CSAnswers.attributeID, Equal<Required<CSAnswers.attributeID>>>>>
.Select(this._Graph, contractRow.NoteID, "DIRINDIREC");
if (cSAnswersRow != null && cSAnswersRow.Value.Equals("IND"))
{
foreach (InventoryItem row in PXSelectJoin<InventoryItem,
InnerJoin<INPostClass,
On<InventoryItem.postClassID, Equal<INPostClass.postClassID>>>,
Where<InventoryItem.itemType, Equal<INItemTypes.expenseItem>,
And<INPostClass.postClassID, Equal<Required<INPostClass.postClassID>>>>>.Select(this._Graph, "IND"))
{
yield return row;
}
}
else
{
foreach (InventoryItem row in PXSelectJoin<InventoryItem,
InnerJoin<INPostClass,
On<InventoryItem.postClassID, Equal<INPostClass.postClassID>>>,
Where<InventoryItem.itemType, Equal<INItemTypes.expenseItem>,
And<INPostClass.postClassID, NotEqual<Required<INPostClass.postClassID>>>>>.Select(this._Graph, "IND"))
{
yield return row;
}
}
}
else
{
foreach (InventoryItem row in PXSelect<InventoryItem,
Where<InventoryItem.itemType, Equal<INItemTypes.expenseItem>>>.Select(this._Graph))
{
yield return row;
}
}
}
}
#endregion
}
The issue appears to be the UI control type of the EPExpenseClaimDetails.InventoryID field.
In the grid RowTemplate ASPX element, InventoryID is declared as a SegmentMask control instead of a Selector control:
<px:PXSegmentMask CommitChanges="True" ID="edInventoryID" runat="server" DataField="InventoryID" AllowEdit="True" Size="XM" />
You should replace the PXSegmentMask control in the grid RowTemplate with a PXSelector control.
Simply removing it will allow the value to stick but you should replace SegmentMask with Selector in order to assign CommitChanges/AllowEdit/Size properties.
This is the control to replace, RowTemplate element is labelled Levels in customization project editor:

DataBind Generic "Dynamic" Class to GridView

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!

Binding the Checkbox.Checked property to property on DataSet

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.

how to bind datasource to List<Dictionary<string, string>>?

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();

Howto determine order of displayed columns in a datagridview binded to a datasource

Is there a way to determine the order of the columns displayed in
a datagridview when binding it to a datasource whitch contains an
underlying IList ?
I thought there was a specific property attribute for this purpose
but can't recall what it actually was.
eg:
public void BindToGrid(IList<CustomClass> list)
{
_bindingSource.DataSource = list;
dataGridView1.DataSource = _bindingSource.DataSource;
}
Type binded should be something like this
class CustomClass
{
bool _selected = false;
//[DisplayOrder(0)]
public bool Selected
{
get { return _selected; }
set { _selected = value; }
}
string _name;
//[DisplayOrder(2)]
public string Name
{
get { return _name; }
set { _name = value; }
}
string _value;
//[DisplayOrder(1)]
public string Value
{
get { return _value; }
set { _value = value; }
}
}
Edit:
I would like to add that I rather not want to add the columns manually to columns list in the designer. I'd like to keep this as dynamic as possible.
In the DataGridView specify an actual list of columns instead of allowing it to auto-databind. You can do this in Design View in Visual Studio by selecting the control and adding the columns. Make sure you specify in each column which property it should bind to. Then you can rearrange the columns any way you want as well as do other customizations.
I think that the DisplayOrder attribute is relatively new and probably not supported in the DataGridView control.
The display order of the columns in the DataGridView is determined by the DisplayIndex properties of the DataGridViewColumn-s. You would have to set these properties on the columns of the grid, in order to change their order.
I also agree with Eilon's answer: you can create the list of the columns yourself, instead of auto-databinding, and that way you can determine the order in which they will be displayed.
The column ordering does not always work. You'll need to turn off AutoColumnCreate to fix inconsistencies:
http://www.internetworkconsulting.net/content/datadridview-displayorder-not-working
I am not sure whether this is a functionality that .Net Offers, but if you just change the order of your properties in the class, the grid renders the columns in the same order.
The below two classes will render in the order they are typed in the class. Strange!!
class CustomClass
{
public bool Selected {get;set;}
public string Name{get;set;}
}
class CustomClass
{
public string Name{get;set;}
public bool Selected {get;set;}
}

Categories