I have a DataTable with complex objects.
For example,
class ComplexDataWrapper
{
public string Name{ get; set; }
public ComplexData Data{ get; set; }
public ComplexDataWrapper(ComplexData data)
{
this.Data = data;
this.Name = "Something";
}
public override string ToString()
{
return Name;
}
}
And now I want to bind cells from DataTable to objects of ComplexDataWrapper
So, I try something like this :
...
var column = new DataColumn() { ColumnName = columnName, DataType = typeof(ComplexDataWrapper)};
row[column] = new ComplexDataWrapper(data);
But, I want to bind for only one property, for example, Name.
And in the gridview (DataTable is a data source for this view) I want to edit this property(Name).
var complexDataWrapper = row[column] as ComplexDataWrapper;
complexDataWrapper always equals to NULL.
I know that I miss something.
So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.
Thanks. Hopefully, everything is clear.
So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.
What you need here is the ability to bind to a so called property path (e.g. obj.Prop1.Prop2). Unfortunately WinForms has limited support for that - it's supported for simple data binding (like control.DataBindings.Add(...)) but not for list data binding which is used by DataGridView control and similar.
Fortunately it's still doable with some (most of the time trivial) coding, because the data binding is build around an abstraction called PropertyDescriptor. By default it is implemented via reflection, but nothing prevents you to create your own implementation and do whatever you like inside it. That allows you to do many things that are not possible with reflection, in particular to simulate "properties" that actually do not exist.
Here we will utilize that possibility to create a "property" that actually gets/sets its value from a child property of the original property, while from outside it still looks like a single property, thus allowing to data bind to it:
public class ChildPropertyDescriptor : PropertyDescriptor
{
public static PropertyDescriptor Create(PropertyDescriptor sourceProperty, string childPropertyPath, string displayName = null)
{
var propertyNames = childPropertyPath.Split('.');
var propertyPath = new PropertyDescriptor[1 + propertyNames.Length];
propertyPath[0] = sourceProperty;
for (int i = 0; i < propertyNames.Length; i++)
propertyPath[i + 1] = propertyPath[i].GetChildProperties()[propertyNames[i]];
return new ChildPropertyDescriptor(propertyPath, displayName);
}
private ChildPropertyDescriptor(PropertyDescriptor[] propertyPath, string displayName)
: base(propertyPath[0].Name, null)
{
this.propertyPath = propertyPath;
this.displayName = displayName;
}
private PropertyDescriptor[] propertyPath;
private string displayName;
private PropertyDescriptor RootProperty { get { return propertyPath[0]; } }
private PropertyDescriptor ValueProperty { get { return propertyPath[propertyPath.Length - 1]; } }
public override Type ComponentType { get { return RootProperty.ComponentType; } }
public override bool IsReadOnly { get { return ValueProperty.IsReadOnly; } }
public override Type PropertyType { get { return ValueProperty.PropertyType; } }
public override bool CanResetValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.CanResetValue(target); }
public override object GetValue(object component) { var target = GetTarget(component); return target != null ? ValueProperty.GetValue(target) : null; }
public override void ResetValue(object component) { ValueProperty.ResetValue(GetTarget(component)); }
public override void SetValue(object component, object value) { ValueProperty.SetValue(GetTarget(component), value); }
public override bool ShouldSerializeValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.ShouldSerializeValue(target); }
public override AttributeCollection Attributes { get { return ValueProperty.Attributes; } }
public override string Category { get { return ValueProperty.Category; } }
public override TypeConverter Converter { get { return ValueProperty.Converter; } }
public override string Description { get { return ValueProperty.Description; } }
public override bool IsBrowsable { get { return ValueProperty.IsBrowsable; } }
public override bool IsLocalizable { get { return ValueProperty.IsLocalizable; } }
public override string DisplayName { get { return displayName ?? RootProperty.DisplayName; } }
public override object GetEditor(Type editorBaseType) { return ValueProperty.GetEditor(editorBaseType); }
public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter) { return ValueProperty.GetChildProperties(GetTarget(instance), filter); }
public override bool SupportsChangeEvents { get { return ValueProperty.SupportsChangeEvents; } }
public override void AddValueChanged(object component, EventHandler handler)
{
var target = GetTarget(component);
if (target != null)
ValueProperty.AddValueChanged(target, handler);
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
var target = GetTarget(component);
if (target != null)
ValueProperty.RemoveValueChanged(target, handler);
}
private object GetTarget(object source)
{
var target = source;
for (int i = 0; target != null && target != DBNull.Value && i < propertyPath.Length - 1; i++)
target = propertyPath[i].GetValue(target);
return target != DBNull.Value ? target : null;
}
}
The code is not so small, but all it does is basically delegating the calls to the corresponding methods of the property descriptor chain representing the path from the original property to the child property. Also please note that many methods of the PropertyDescriptor are used only during the design time, so creating a custom concrete runtime property descriptor usually needs only to implement ComponentType, PropertyType, GetValue and SetValue (if supported).
So far so good. This is just the first part of the puzzle. We can create a "property", now we need a way to let data binding use it.
In order to do that, we'll utilize another data binding related interface called ITypedList:
Provides functionality to discover the schema for a bindable list, where the properties available for binding differ from the public properties of the object to bind to.
In other words, it allows us to provide "properties" for the list elements. But how? If we were implementing the data source list, it would be easy. But here we want to do that for a list that we don't know in advance (I'm trying the keep the solution generic).
The solutions is to wrap the original list in onother one that will implement IList (the minimum requirement for list data binding) by delegating all the calls to the underlying list, but by implementing ITypedList will control the properties used for binding:
public static class ListDataView
{
public static IList Create(object dataSource, string dataMember, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper)
{
var source = (IList)ListBindingHelper.GetList(dataSource, dataMember);
if (source == null) return null;
if (source is IBindingListView) return new BindingListView((IBindingListView)source, propertyMapper);
if (source is IBindingList) return new BindingList((IBindingList)source, propertyMapper);
return new List(source, propertyMapper);
}
private class List : IList, ITypedList
{
private readonly IList source;
private readonly Func<PropertyDescriptor, PropertyDescriptor> propertyMapper;
public List(IList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) { this.source = source; this.propertyMapper = propertyMapper; }
// IList
public object this[int index] { get { return source[index]; } set { source[index] = value; } }
public int Count { get { return source.Count; } }
public bool IsFixedSize { get { return source.IsFixedSize; } }
public bool IsReadOnly { get { return source.IsReadOnly; } }
public bool IsSynchronized { get { return source.IsSynchronized; } }
public object SyncRoot { get { return source.SyncRoot; } }
public int Add(object value) { return source.Add(value); }
public void Clear() { source.Clear(); }
public bool Contains(object value) { return source.Contains(value); }
public void CopyTo(Array array, int index) { source.CopyTo(array, index); }
public IEnumerator GetEnumerator() { return source.GetEnumerator(); }
public int IndexOf(object value) { return source.IndexOf(value); }
public void Insert(int index, object value) { source.Insert(index, value); }
public void Remove(object value) { source.Remove(value); }
public void RemoveAt(int index) { source.RemoveAt(index); }
// ITypedList
public string GetListName(PropertyDescriptor[] listAccessors) { return ListBindingHelper.GetListName(source, listAccessors); }
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
var properties = ListBindingHelper.GetListItemProperties(source, listAccessors);
if (propertyMapper != null)
properties = new PropertyDescriptorCollection(properties.Cast<PropertyDescriptor>()
.Select(propertyMapper).Where(p => p != null).ToArray());
return properties;
}
}
private class BindingList : List, IBindingList
{
private IBindingList source;
public BindingList(IBindingList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
private ListChangedEventHandler listChanged;
public event ListChangedEventHandler ListChanged
{
add
{
var oldHandler = listChanged;
if ((listChanged = oldHandler + value) != null && oldHandler == null)
source.ListChanged += OnListChanged;
}
remove
{
var oldHandler = listChanged;
if ((listChanged = oldHandler - value) == null && oldHandler != null)
source.ListChanged -= OnListChanged;
}
}
private void OnListChanged(object sender, ListChangedEventArgs e)
{
var handler = listChanged;
if (handler != null)
handler(this, e);
}
public bool AllowNew { get { return source.AllowNew; } }
public bool AllowEdit { get { return source.AllowEdit; } }
public bool AllowRemove { get { return source.AllowRemove; } }
public bool SupportsChangeNotification { get { return source.SupportsChangeNotification; } }
public bool SupportsSearching { get { return source.SupportsSearching; } }
public bool SupportsSorting { get { return source.SupportsSorting; } }
public bool IsSorted { get { return source.IsSorted; } }
public PropertyDescriptor SortProperty { get { return source.SortProperty; } }
public ListSortDirection SortDirection { get { return source.SortDirection; } }
public object AddNew() { return source.AddNew(); }
public void AddIndex(PropertyDescriptor property) { source.AddIndex(property); }
public void ApplySort(PropertyDescriptor property, ListSortDirection direction) { source.ApplySort(property, direction); }
public int Find(PropertyDescriptor property, object key) { return source.Find(property, key); }
public void RemoveIndex(PropertyDescriptor property) { source.RemoveIndex(property); }
public void RemoveSort() { source.RemoveSort(); }
}
private class BindingListView : BindingList, IBindingListView
{
private IBindingListView source;
public BindingListView(IBindingListView source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
public string Filter { get { return source.Filter; } set { source.Filter = value; } }
public ListSortDescriptionCollection SortDescriptions { get { return source.SortDescriptions; } }
public bool SupportsAdvancedSorting { get { return source.SupportsAdvancedSorting; } }
public bool SupportsFiltering { get { return source.SupportsFiltering; } }
public void ApplySort(ListSortDescriptionCollection sorts) { source.ApplySort(sorts); }
public void RemoveFilter() { source.RemoveFilter(); }
}
}
Actually as you can see, I've added wrappers for other data source interfaces like IBindingList and IBindingListView. Again, the code is not so small, but it's just delegating the calls to the underlying objects (when creating one for your concrete data, you usually would inherit from List<T> or BiundingList<T> and implement only the two ITypedList members). The essential part is the GetItemProperties method implementation which along with the propertyMapper lambda allows you to replace one property with another.
With all that in place, solving the specific problem from the post is simple a matter of wrapping the DataTable and mapping the Complex property to the Complex.Name property:
class ComplexData
{
public int Value { get; set; }
}
class ComplexDataWrapper
{
public string Name { get; set; }
public ComplexData Data { get; set; } = new ComplexData();
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var gridView = new DataGridView { Dock = DockStyle.Fill, Parent = form };
gridView.DataSource = ListDataView.Create(GetData(), null, p =>
{
if (p.PropertyType == typeof(ComplexDataWrapper))
return ChildPropertyDescriptor.Create(p, "Name", "Complex Name");
return p;
});
Application.Run(form);
}
static DataTable GetData()
{
var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Complex", typeof(ComplexDataWrapper));
for (int i = 1; i <= 10; i++)
dt.Rows.Add(i, new ComplexDataWrapper { Name = "Name#" + i, Data = new ComplexData { Value = i } });
return dt;
}
}
To recap, custom PropertyDescriptor and ITypedList allow you to create unlimited types of views of your data, which then can be used by any data bound aware control.
I believe your architecture is flawed for what you're trying to achieve.
If you are using the gridView to edit a single property on your complex type, then there is no need to bind the entire type into the datatable the is the datasource of your grid.
Instead you should bind only the property you wish to edit, and when the the data comes back, simply assign it to the complex type in the right place.
Related
I am trying to implement Linda Liu's solution for displaying second-level properties in a DataGridView. If I'm understanding everything correctly, when a DataSource is set, C# uses a method called ICustomTypeDescriptor.GetProperties, which "grabs" PropertyDescriptors from the class module and adds them to a collection. So Liu's solution is to create PropertyDescriptors for the second level properties and to return them along with the original PropertyDescriptors.
In my case, EmployeeModel has a property JobTitle. JobTitle has properties Name and ID. My SQL joins these and returns a list of EmployeeModel with JobTitle.Name and JobTitle.ID set. I want to display JobTitle.Name as a column in EmployeeGridView. (When I've managed to make this work, I'll do the same for several other properties of EmployeeModel.)
Everything compiles, but at runtime the effect is not achieved. I end up with an extra column with no visible content, while the original JobTitle column is replicated as the rightmost column.
Screenshot of my application
namespace WorkDesk_Library.Binding_Assist
{
class MyTypeDescriptionProvider : TypeDescriptionProvider
{
private ICustomTypeDescriptor td;
public MyTypeDescriptionProvider()
: this(TypeDescriptor.GetProvider(typeof(EmployeeModel)))
{
}
public MyTypeDescriptionProvider(TypeDescriptionProvider parent)
: base(parent)
{
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
if (td == null)
{
td = base.GetTypeDescriptor(objectType, instance);
td = new MyCustomTypeDescriptor(td);
}
return td;
}
}
}
namespace WorkDesk_Library.Binding_Assist
{
class MyCustomTypeDescriptor : CustomTypeDescriptor
{
public MyCustomTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent)
{
}
public override PropertyDescriptorCollection GetProperties()
{
PropertyDescriptorCollection cols = base.GetProperties();
PropertyDescriptor JobTitle = cols["JobTitle"];
PropertyDescriptorCollection JobTitle_child = JobTitle.GetChildProperties();
PropertyDescriptor[] array = new PropertyDescriptor[cols.Count + 2];
cols.CopyTo(array, 0);
array[cols.Count] = new SubPropertyDescriptor(JobTitle, JobTitle_child["Name"], "JobTitle_Name");
array[cols.Count + 1] = new SubPropertyDescriptor(JobTitle, JobTitle_child["ID"], "JobTitle_ID");
PropertyDescriptorCollection newcols = new PropertyDescriptorCollection(array);
return newcols;
}
}
}
namespace WorkDesk_Library.Interfaces
{
class SubPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor _subPD;
private PropertyDescriptor _parentPD;
public SubPropertyDescriptor(PropertyDescriptor parentPD, PropertyDescriptor subPD, string pdname)
: base(pdname, null)
{
_subPD = subPD;
_parentPD = parentPD;
}
public override Type ComponentType
{
get { return _parentPD.ComponentType; }
}
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType
{
get { return _subPD.PropertyType; }
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return _subPD.GetValue(_parentPD.GetValue(component));
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
_subPD.SetValue(_parentPD.GetValue(component), value);
OnValueChanged(component, EventArgs.Empty);
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
}
}
public void CreateEmployeeTable()
{
EmployeeGridView.DataSource = globalEmployeeList;
EmployeeGridView.Columns[0].HeaderText = "Employee ID";
EmployeeGridView.Columns["ID"].DisplayIndex = 0;
EmployeeGridView.Columns["ListView"].DisplayIndex = 1;
EmployeeGridView.Columns["ListView"].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
EmployeeGridView.Columns["Department"].DisplayIndex = 2;
EmployeeGridView.Columns["JobTitle"].DisplayIndex = 3;
EmployeeGridView.Columns["JobTitle"].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
EmployeeGridView.Columns["Nickname"].Visible = false;
EmployeeGridView.Columns["FirstName"].Visible = false;
EmployeeGridView.Columns["LastName"].Visible = false;
EmployeeGridView.Columns["HireDate"].Visible = false;
}
I am looking for a way to cast object variable into type with generic type argument specified by other variable of type Type.
I am limited to .NET 3.5, so no dynamic can be used :(
Main idea here is that I have access to a dictionary:
Dictionary<Type, object> data;
Data to that dictionary is added only in form of:
data.Add(T, new DataSub<T>(someValueOfTypeT));
The problem is, that when I'm trying to reverse the process:
foreach(var dataType in data.Keys) {
var dataValue = data[dataType];
ProcessDataValue(dataType, dataValue);
}
Now the question is how do I manage to cast object to DataSub?
Simplified DataSub.cs:
public class DataSub<T>
{
private T _cache;
public T Value {
get { return _cache; }
set { _cache = value; }
}
}
How it could work in ProcessDataValue:
public void ProcessDataValue(Type dataType, object dataValue)
{
var data = dataValue as DataSub<dataType>;
if (data == null) return;
AddProcessedDataValue(dataType, data.Value.ToString());
}
if you can do minimal changes to the classes you posted and if - as is showed in your example - what you would do with DataSub.Value is invoking ToString, may be you can obtain the result you need with
public interface IDataSub {
bool MatchesType(Type t);
object GetValue();
}
public class DataSub<T> : IDataSub {
private T _cache;
public T Value {
get { return _cache; }
set { _cache = value; }
}
public bool MatchesType(Type t) {
return typeof(T) == t; // or something similar, in order to handle inheritance
}
public object GetValue() {
return Value;
}
}
public class Client {
Dictionary<Type, IDataSub> data = new Dictionary<Type, IDataSub>() ;
public void AddData<T>(T someValueOfTypeT) {
data.Add(typeof(T), new DataSub<T> { Value = someValueOfTypeT });
}
public void UseData() {
foreach(var dataType in data.Keys) {
var dataValue = data[dataType];
ProcessDataValue(dataType, dataValue);
}
}
public void ProcessDataValue(Type dataType, IDataSub dataValue)
{
if(dataValue.MatchesType(dataType))
AddProcessedDataValue(dataType, dataValue.GetValue().ToString());
}
}
If the usage of DataSub.Value.ToString is only an example, and in the real world you need to access DataSub.Value using its type T, you should apply a broader reworking of you code.
What do you think about the following approach? This is an application of the pattern I like call set of responsibility (I wrote the linked post about this topic), a variation of GoF's chain of responsibility:
public interface IDataSub {
object GetValue();
}
public class DataSub<T> : IDataSub {
private T _cache;
public T Value {
get { return _cache; }
set { _cache = value; }
}
public object GetValue() {
return Value;
}
}
public interface IDataHandler {
bool CanHandle(Type type);
void Handle(object data);
}
public class Client {
private readonly Dictionary<Type, IDataSub> data = new Dictionary<Type, IDataSub>();
private readonly IList<IDataHandler> handlers = new List<IDataHandler>();
public void AddData<T>(T someValueOfTypeT) {
data.Add(typeof(T), new DataSub<T> { Value = someValueOfTypeT });
}
public void RegisterHandler(IDataHandler handler) {
handlers.Add(handler);
}
public void UseData() {
foreach(var dataType in data.Keys) {
handlers.FirstOrDefault(h => h.CanHandle(dataType))?.Handle(data[dataType].GetValue());
}
}
// Lambda-free version
// public void UseData() {
// foreach(var dataType in data.Keys) {
// for (int i = 0; i < handlers.Count; i++) {
// if (handlers[i].CanHandle(dataType)) {
// handlers[i].Handle(data[dataType].GetValue());
// break; // I don't like breaks very much...
// }
// }
// }
// }
}
class StringDataHandler : IDataHandler {
public bool CanHandle(Type type) {
// Your logic to check if this handler implements logic applyable to instances of type
return typeof(string) == type;
}
public void Handle(object data) {
string value = (string) data;
// Do something with string
}
}
class IntDataHandler : IDataHandler {
public bool CanHandle(Type type) {
// Your logic to check if this handler implements logic applyable to instances of type
return typeof(int) == type;
}
public void Handle(object data) {
int value = (int) data;
// Do something with int
}
}
This approach allow you to decouple data storage and data iteration logic from data-handling logic specific of different data-types: IDataHandler's implementations known what type of data they can handle and cast generic object reference to desired type. If you prefer, you can merge CanHandle method into Handle method, remving the former method and changing UseData to
public void UseData() {
foreach(var dataType in data.Keys) {
foreach(var handler in handlers) {
handler.Handle(dataType, data[dataType].GetValue())
}
}
}
and handler implementations to
class IntDataHandler : IDataHandler {
public void Handle(Type dataType, object data) {
if(typeof(int) == type) {
int value = (int) data;
// Do something with int
}
}
}
This variant is slightly more type-safe, because in the first variant was already possibile to call Handle method without a previus call to CanHandle.
If you liked this approach, you can bring it forward, simplifying your data structure and converting data from IDictionary to IList:
public interface IDataSub {
object GetValue();
}
public class DataSub<T> : IDataSub {
private T _cache;
public T Value {
get { return _cache; }
set { _cache = value; }
}
public object GetValue() {
return Value;
}
}
public interface IDataHandler {
bool CanHandle(object data);
void Handle(object data);
}
public class Client {
private readonly IList<IDataSub> data = new List<IDataSub>();
private readonly IList<IDataHandler> handlers = new List<IDataHandler>();
public void AddData<T>(T someValueOfTypeT) {
data.Add(new DataSub<T> { Value = someValueOfTypeT });
}
public void RegisterHandler(IDataHandler handler) {
handlers.Add(handler);
}
public void UseData() {
foreach(var dataItem in data) {
var value = dataItem.GetValue();
handlers.FirstOrDefault(h => h.CanHandle(value))?.Handle(value);
}
}
// Lambda-free version as above...
class StringDataHandler : IDataHandler {
public bool CanHandle(object data) {
// Your logic to check if this handler implements logic applyable to instances of String
return data is string;
}
public void Handle(object data) {
string value = (string) data;
// Do something with string
}
}
class IntDataHandler : IDataHandler {
public bool CanHandle(Type type) {
// Your logic to check if this handler implements logic applyable to instances of int
return type is int;
}
public void Handle(object data) {
int value = (int) data;
// Do something with int
}
}
The CanHandle-free variant can simplify IDataHandler interface and its implementation in this case, too...
I hope my answer can help you resolving you design scenario; I build it upon an approach I like very much, because it allows to apply subtype-specific logic to instances of different classe, given they share a common superclass (as object in my code samples).
I have an abstract class like this:
public abstract class Records
{
public string Type;
public string Source;
public int Value;
protected Records(string type, string source, int value)
{
Type = type;
Source = source;
Value = value;
}
}
I would like to create many classes inheriting this class, and filling their Type field with a value coming from a static class like this:
public static class ContentTypesString
{
public static string DocumentNew { get { return "Document - New this Month"; }}
public static string HeadlinesNew { get { return "Headlines - New this Month"; }}
etc...
}
I would like to be able to create those child classes without having a test "if foo == "document" then type = ContentTypesString.DocumentNew" or an equivalent switch case (I really have a lot of cases)
Is there a design pattern that suits my needs?
EDIT : As several people pointed out, i should show how i create my instances.
private delegate SPListItemCollection Query(SPWeb web, DateTime startDate, DateTime endDate);
private readonly Query _queries;
#region Constructors
public QueryHandler(SPWeb web, DateTime startTimeSelectedDate, DateTime endTimeSelectedDate)
{
if (web == null) throw new ArgumentNullException("web");
_web = web;
_startTimeSelectedDate = startTimeSelectedDate;
_endTimeSelectedDate = endTimeSelectedDate;
RecordsList = new List<Records>();
// Query Invocation List
_queries = NumberPagePerMonthQuery.PreparedQuery;
_queries += NumberDocumentsPerMonthQuery.PreparedQuery;
_queries += NumberHeadlinesPerMonthQuery.PreparedQuery;
_queries += NumberLeaderboxPerMonthQuery.PreparedQuery;
_queries += NumberNewsPerMonthQuery.PreparedQuery;
_queries += NumberPagesModifiedPerMonthQuery.PreparedQuery;
_queries += NumberPicturesPerMonthQuery.PreparedQuery;
_queries += NumberTeasingPerMonthQuery.PreparedQuery;
}
#endregion Constructors
#region Public Methods
// what about NullReferenceException ? C#6 : item?.Foreach(item => {}); ?
/*** NO C#6 compiler in VS2012... ***/
public void Queries()
{
foreach (var del in _queries.GetInvocationList())
{
var queryresult =
(SPListItemCollection) del.DynamicInvoke(_web, _startTimeSelectedDate, _endTimeSelectedDate);
RecordsList.Add(new Records(del.Method.Name, _web.Title, queryresult.Count));
}
}
EDIT² :
The solution i chose
public List<IQuery> QueryList { get; } // no delegate anymore, and static classes became implementations of IQuery interface.
#region Constructors
public QueryHandler(SPWeb web, DateTime startTimeSelectedDate, DateTime endTimeSelectedDate)
{
if (web == null) throw new ArgumentNullException("web");
_web = web;
_startTimeSelectedDate = startTimeSelectedDate;
_endTimeSelectedDate = endTimeSelectedDate;
RecordsList = new List<Records>();
QueryList = new List<IQuery>
{
new NumberDocumentsPerMonthQuery(),
new NumberHeadlinesPerMonthQuery(),
new NumberLeaderboxPerMonthQuery(),
new NumberNewsPerMonthQuery(),
new NumberPagePerMonthQuery(),
new NumberPagesModifiedPerMonthQuery(),
new NumberPicturesPerMonthQuery(),
new NumberTeasingPerMonthQuery()
};
}
#endregion Constructors
#region Public Methods
// what about NullReferenceException ? C#6 : item?.Foreach(item => {}); ?
/*** NO C#6 compiler in VS2012... ***/
public void Queries()
{
foreach (var query in QueryList)
{
var queryresult = query.PreparedQuery(_web, _startTimeSelectedDate, _endTimeSelectedDate);
RecordsList.Add(query.CreateRecord(_web.Title, queryresult.Count));
}
}
Record class follow the implementation suggested by #dbraillon
Implementation of IQuery interface were added the method :
public Records CreateRecord(string source, int value)
{
return new ModifiedPagesPerMonthRecord(source, value); //or another child of Record class.
}
And voilà. Thank you all for the help.
You want to make collection of records, by string code of object type, and parameters.
One of many way to do it - use builder.
Firstly we need to configurate builder:
var builder = new RecordBuilder()
.RegisterBuilder("document", (source, value) => new Document(source, value))
.RegisterBuilder("headlines", (source, value) => new Headlines(source, value));
here we specify how to build record with code "document" and "headlines".
To build a record call:
builder.Build("document", "source", 1);
Builder code can by something like this
(here we look if we know how to build record of the passed type and make it):
public class RecordBuilder
{
public Records Build(string code, string source, int value)
{
Func<string, int, Records> buildAction;
if (recordBuilders.TryGetValue(code, out buildAction))
{
return buildAction(source, value);
}
return null;
}
public RecordBuilder RegisterBuilder(string code, Func<string, int, Records> buildAction)
{
recordBuilders.Add(code, buildAction);
return this;
}
private Dictionary<string, Func<string, int, Records>> recordBuilders = new Dictionary<string, Func<string, int, Records>> ();
}
public class Document : Records
{
public Document(string source, int value) : base(ContentTypesString.DocumentNew, source, value)
{
}
}
public class Headlines : Records
{
public Headlines(string source, int value) : base(ContentTypesString.HeadlinesNew, source, value)
{
}
}
Is that what you need ?
public abstract class Records
{
public string Type;
public string Source;
public int Value;
protected Records(string type, string source, int value)
{
Type = type;
Source = source;
Value = value;
}
}
public class DocumentRecords : Records
{
public DocumentRecords(string source, int value)
: base(ContentTypesString.DocumentNew, source, value) // use here
{
}
}
public class HeadlinesRecords : Records
{
public HeadlinesRecords(string source, int value)
: base(ContentTypesString.HeadlinesNew, source, value) // use here
{
}
}
public static class ContentTypesString
{
public static string DocumentNew { get { return "Document - New this Month"; } }
public static string HeadlinesNew { get { return "Headlines - New this Month"; } }
}
To explain my case, let's consider a simple object like this:
public class FixedSeries : Series
{
int val1, val2;
public FixedSeries(int val1, int val2) { this.val1 = val1; this.val2 = val2; }
public int Diff
{
get { return val2 - val1; }
set { val2 = val1 + value; }
}
}
Then, if in my form I want to bind Diff to a control's value I can do:
BindingSource source;
FixedSeries fixedSeries;
public Form1()
{
InitializeComponent();
fixedSeries = new FixedSeries(2, 5);
source = new BindingSource();
source.DataSource = fixedSeries;
numericUpDown1.DataBindings.Add(new System.Windows.Forms.Binding("Value", source, "Diff", false, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
}
However, if my FixedSeries derive from a more generic Series (see here below) that implements the ICollection<int> interface I get an ArgumentException "Cannot bind to the property or column Diff on the DataSource".
public class FixedSeries : Series
{
public FixedSeries(int val1, int val2)
{
base.Add(val1);
base.Add(val2);
}
public int Diff
{
get { return base[1] - base[0]; }
set { base[1] = base[0] + value; }
}
}
public interface ISeries : ICollection<int>
{
int this[int index] { get; }
}
public class Series : ISeries
{
List<int> vals = new List<int>();
public int this[int index]
{
get { return vals[index]; }
internal set { vals[index] = value; }
}
public void Add(int item) { vals.Add(item); }
public void Clear() { vals.Clear(); }
public bool Contains(int item) { return vals.Contains(item); }
public void CopyTo(int[] array, int arrayIndex) { vals.CopyTo(array, arrayIndex); }
public int Count { get { return vals.Count; } }
public bool IsReadOnly { get { return false; } }
public bool Remove(int item) { return vals.Remove(item); }
public IEnumerator<int> GetEnumerator() { return vals.GetEnumerator(); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return vals.GetEnumerator(); }
}
I guess this has something to do with the ICollection<T> interface and maybe to the fact that .NET expects to bind to items inside. How can I bind to the Diff property in this scenario without removing still the possibilities to bind to items inside the series?
I guess this has something to do with the ICollection<T> interface and maybe to the fact that .NET expects to bind to items inside.
Correct. More precisely the IEnumerable<T> which ICollection<T> inherits.
How can I bind to the Diff property in this scenario without removing still the possibilities to bind to items inside the series?
Here are some options
(A) Do not use the BindingSource, bind directly to the data source object
numericUpDown1.DataBindings.Add("Value", fixedSeries, "Diff", false, DataSourceUpdateMode.OnPropertyChanged);
(B) Wrap the data source object into a single item array/list and use that as BindingSource.DataSource
source.DataSource = new[] { fixedSeries };
(C) Similar to (B), but use BindingSource.Add method (w/o supplying DataSource property at all)
source.Add(fixedSeries);
I'm trying to add a TypeDescriptor Provider for a class that I use how datasource in my datagridview, with this code:
private void FrmTarifadoVariantes_Load(object sender, EventArgs e)
{
System.ComponentModel.TypeDescriptor.AddProvider((new MyTypeDescriptionProvider<TarifadoVarianteBE>()), typeof(TarifadoBloquesBE));
System.ComponentModel.TypeDescriptor.AddProvider((new MyTypeDescriptionProvider<BloquePrendaBE>()), typeof(TarifadoBloquesBE));
}
QUESTION 1:
First time all is good, but when I closed the form and open again, the data provider descriptor twice columns, I tried to remove first the providers and then add again, but It doesn't work.
There is MyTypeDescriptionProvider, that I found searching on web:
public class MyTypeDescriptionProvider<T> : TypeDescriptionProvider
{
private ICustomTypeDescriptor td;
public MyTypeDescriptionProvider()
: this(TypeDescriptor.GetProvider(typeof(TarifadoBloquesBE)))
{
}
public MyTypeDescriptionProvider(TypeDescriptionProvider parent)
: base(parent)
{
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
if (td == null)
{
td = base.GetTypeDescriptor(objectType, instance);
td = new MyCustomTypeDescriptor(td, typeof(T));
}
return td;
}
}
public class SubPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor _subPD;
private PropertyDescriptor _parentPD;
public SubPropertyDescriptor(PropertyDescriptor parentPD, PropertyDescriptor subPD, string pdname)
: base(pdname, null)
{
_subPD = subPD;
_parentPD = parentPD;
}
public override bool IsReadOnly { get { return false; } }
public override void ResetValue(object component) { }
public override bool CanResetValue(object component) { return false; }
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return _parentPD.ComponentType; }
}
public override Type PropertyType { get { return _subPD.PropertyType; } }
public override object GetValue(object component)
{
return _subPD.GetValue(_parentPD.GetValue(component));
}
public override void SetValue(object component, object value)
{
_subPD.SetValue(_parentPD.GetValue(component), value);
OnValueChanged(component, EventArgs.Empty);
}
}
public class MyCustomTypeDescriptor : CustomTypeDescriptor
{
Type typeProperty;
public MyCustomTypeDescriptor(ICustomTypeDescriptor parent, Type type)
: base(parent)
{
typeProperty = type;
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection cols = base.GetProperties(attributes);
string propertyName = "";
foreach (PropertyDescriptor col in cols)
{
//se agregó si el attributo es browsable
if (col.IsBrowsable)
{
if (col.PropertyType.Name == typeProperty.Name)
{
propertyName = col.Name;
break;
}
}
}
PropertyDescriptor pd = cols[propertyName];
PropertyDescriptorCollection children = pd.GetChildProperties();
PropertyDescriptor[] array = new PropertyDescriptor[cols.Count + children.Count];
int count = cols.Count;
cols.CopyTo(array, 0);
foreach (PropertyDescriptor cpd in children)
{
if (cpd.IsBrowsable)
{
array[count] = new SubPropertyDescriptor(pd, cpd, pd.Name + "_" + cpd.Name);
count++;
}
}
//PropertyDescriptor[] arrayTmp = new PropertyDescriptor[count];
Array.Resize(ref array, count);
PropertyDescriptorCollection newcols = new PropertyDescriptorCollection(array);
return newcols;
}
}
QUESTION 2:
How can I make MyTypeDescriptionProvider dinamyc, I mean if you can check, I hard code the class (TarifadoBloquesBE) of the GetProvider, in this part:
private ICustomTypeDescriptor td;
public MyTypeDescriptionProvider()
: this(TypeDescriptor.GetProvider(typeof(TarifadoBloquesBE)))
{
}
I want to allow sending what class will be the provider. Thank you.
I attached two images, first one is ok, the next one you can check twice columns, just seeing the scroll bar.
All good here
Duplicate columns