Home - Forums-.NET - FlyGrid.Net (Windows Forms) - TypeConverter for databound objects

FlyGrid.Net (Windows Forms)

.NET Datagrid - Fast, highly customizable, industry standards .NET data grid control for WinForms

This forum related to following products: FlyGrid.Net

TypeConverter for databound objects
Link Posted: 28-Jun-2006 04:49
Hi,

One of the columns of my grid is inherited from DynamicallyAutoDetectDataTypeColumn. The grid works in databound mode (FlyGrid.Rows.DataSource=MyIBindingListImplementation;).
The column is bound to the field of type object and must provide appropriate inplace editors for underlying types.
For standard types everything works perfect.
Now I want to customize the behavior of the column for some of my own special types. I implement a custom type converter and attach it to my custom data type:

// This is a type, which represents a databound list item
public class MyDataClass
{
  //...
  private object _DataField;  // this can be instance of MyCustomType
  public object DataField
  {
    get{ return _DataField; }
    set{ _DataField = value; }
  }
  //...
}

[TypeConverter(typeof(MyCustomType.MyCustomTypeConverter))]
public class MyCustomType
{
  internal class MyCustomTypeConverter : Int32Converter
  {
    //....
    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
      if (destinationType == typeof(string))
      {
        //...
        return value.ToString();
      }
      return base.ConvertTo (context, culture, value, destinationType);
    }
  }
  //...
}


During execution I get strange behaviour: sometimes the type of the value object passed to the CovertTo() method is of MyCustomType type and sometimes (it happens approximately once per two datagrid reloads) the type of the value object is the container type MyDataClass, whose property value I try to convert. (object value is the reference either to the MyCustomType or to the grid's row MyDataClass instance).

So, I have two questions:
1. When to expect the type of the value passed to the ConvertTo() method to be of a property type and when of property container type?
2. How to determine a parent instance of the property instance in the ConvertTo() method?
In any case, inside of ConvertTo() method
context.Instance equals value,
context.PropertyDescriptor correctly describes property being converted
context.Container == null.
Thus, it seems there is no helpful information about context present in the ConvertTo() method.
Link Posted: 28-Jun-2006 06:18

1. When to expect the type of the value passed to the ConvertTo() method to be of a property type and when of property container type?

DynamicallyAutoDetectDataTypeColumn uses TypeConverter.ConvertToString(ITypeDescriptorContext, object) when displays data in cell, and TypeConverter.ConvertFrom(ITypeDescriptorContext, CultureInfo, object) when converts string value to the native type of node's cell.

2. How to determine a parent instance of the property instance in the ConvertTo() method?

DynamicallyAutoDetectDataTypeColumn provides own context when uses TypeConverter

In any case, inside of ConvertTo() method
context.Instance equals value,
context.PropertyDescriptor correctly describes property being converted
context.Container == null.
Thus, it seems there is no helpful information about context present in the ConvertTo() method.

Currently you can override DynamicallyAutoDetectDataTypeColumn.GetContext(NodeBase node) to provide your own ITypeDescriptorContext implementation, but with the nearest FlyGrid.Net update you can receive more information, by using following code:
[c#]
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
   NodeBase currentNode = context.GetService(typeof(NodeBase));
   Column currentColumn = context.GetService(typeof(Column));
   //.....
}
Link Posted: 28-Jun-2006 20:33
[quote="NineRays"]

1. When to expect the type of the value passed to the ConvertTo() method to be of a property type and when of property container type?

DynamicallyAutoDetectDataTypeColumn uses TypeConverter.ConvertToString(ITypeDescriptorContext, object) when displays data in cell, and TypeConverter.ConvertFrom(ITypeDescriptorContext, CultureInfo, object) when converts string value to the native type of node's cell.


ConvertToString() method is not virtual, and
ConvertTo() method is used in the example here:
http://9rays.net/support/downloads/FGAutoColumnAndDropDownEditors.zip

[quote="NineRays"]

In any case, inside of ConvertTo() method
context.Instance equals value,
context.PropertyDescriptor correctly describes property being converted
context.Container == null.
Thus, it seems there is no helpful information about context present in the ConvertTo() method.

Currently you can override DynamicallyAutoDetectDataTypeColumn.GetContext(NodeBase node) to provide your own ITypeDescriptorContext implementation, but with the nearest FlyGrid.Net update you can receive more information, by using following code:
[c#]
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
   NodeBase currentNode = context.GetService(typeof(NodeBase));
   Column currentColumn = context.GetService(typeof(Column));
   //.....
}


Thank you. I am gratefully waiting for update.
Link Posted: 28-Jun-2006 23:56
ConvertToString() method is not virtual, and
ConvertTo() method is used in the example here:
http://9rays.net/support/downloads/FGAutoColumnAndDropDownEditors.zip

Yes, but TypeConverter.ConvertToString(ITypeDescriptorContext, object) uses virtual method TypeConverter.ConvertTo(ITypeDescriptorContext, CultureInfo, object, Type) to convert object to string.
I am gratefully waiting for update.

Thanks, we're planning to update FlyGrid.Net tomorrow.
Link Posted: 04-Jul-2006 02:36
Thank you. The update provides context information well, although it does not fix problem with arbitrary type of the value passed to the ConvertTo() method. And I suspect that the type expected to be returned from ConvertFrom() method is also random (either property container type or property value type), which probably could cause other mistakes.

But still I am experiencing certain problems.

I have Windows.Form with single FlyGrid.
FlyFrid has two columns: HierarchyColumn (doesn't really matters) and MyCustomColumn inherited from DynamicallyAutoDetectDataTypeColumn:

public class MyCustomColumn : DynamicallyAutoDetectDataTypeColumn
{
  public override Type OnGetDataType(NodeBase node)
  {
    //(*2*)  if( node.Value is DataString )
    //(*2*)    return typeof(string);

    Type res = base.OnGetDataType (node);
    return res;
  }

  public override EditorStyle OnGetEditorStyle(NodeBase node)
  {
    //if( node.Value is DataString )
    //  return EditorStyle.DropDown;
    return base.OnGetEditorStyle(node);
  }

  public override object GetValue(NodeBase node)
  {
    //(*1*)  if( node.Value is DataString )
    //(*1*)  {
    //(*1*)  DataString ds = node.Value as DataString;
    //(*1*)  return ds.Value.ToString();
    //(*1*)  }

    return base.GetValue (node);
  }

  public override string GetTextValue(NodeBase node)
  {
    //(*1*)  if( node.Value is DataString )
    //(*1*)  {
    //(*1*)  DataString ds = node.Value as DataString;
    //(*1*)  return ds.Value.ToString();
    //(*1*)  }

    return base.GetTextValue (node);
  }
}


The grid in this sample is bound to the ArrayList (but situation is the same if a IBindingList implementation is used as DataSource):

private void Form1_Load(object sender, System.EventArgs e)
{
      
  ArrayList _List = new ArrayList();
                
          Data d = new DataInt();
  _List.Add( d );

  d = new DataString();
  _List.Add( d );

  d = new DataInt();
  _List.Add( d );

  d = new DataString();
  _List.Add( d );
      
  flyGrid1.Rows.DataSource = _List;
}


The data rows in the data source are of two types DataInt and DataString, which both have common parent:


public abstract class Data
{
  private string _ID;
  public string DisplayName
  {
    get{ return _ID; }
    set{ _ID = value; }
  }

  protected object _Value;
  public virtual object Value
  {
    get{ return _Value; }
    set{ _Value = value; }
  }

  public Data()
  {
    _ID = \"Name\" + GetNextId();
    _Value = null;
  }

  public static int GetNextId()
  {
    return CurId++;
  }

  private static int CurId;

  static Data()
  {
    CurId = 1;
  }
}

public class DataInt : Data
{
  public DataInt()
  {
    _Value = GetNextId();
  }

  public override object Value
  {
    get{ return base.Value; }
    set{ base.Value = value; }
  }
}

public class DataString : Data
{
  public DataString()
  {
    _Value = \"s\"+GetNextId().ToString();
  }

  public override object Value
  {
    get{ return base.Value; }
    set{ base.Value = value; }
  }
}
Link Posted: 04-Jul-2006 02:42
In this case (implementation of MyCustomColumn completely relies on the parent method implementations) after application startup the grid on my machine shows following:

DisplayName,   Value
Name1,             2  
Name3,            testFlyGrid.DataString
Name5,             6
Name7,             testFlyGrid.DataString


If to uncomment lines marked with (*1*), then instead of testFlyGrid.DataString grid shows correct DataString.Value values:

DisplayName,   Value
Name1,             2  
Name3,             s4
Name5,             6
Name7,             s8

BUT it is not possible to save changes to fields s4 and s8. You must press ESC in order to discard changes and leave cell.

if to uncomment additionally lines marked with (*2*), then it is possible to leave a s4 or s8 cell without invoking ESC , but changes are not saved and eventually it is not possible to edit these cells too.

How to make different types be editable properly?
Where is my mistake?

Thank you.
Link Posted: 05-Jul-2006 19:06
It will enough if you provide TypeConverter object for types used to display in DynamicallyAutoDetectDataTypeColumn.
[c#]
[TypeConverter(typeof(DataIntConverter))]
public class DataInt : Data
{
  public DataIntConverter : TypeConverter
  {
    //....
  }
  //.....
}


TypeConverter should provide data conversion from/to string.
Link Posted: 05-Jul-2006 19:56
[quote="NineRays"]It will enough if you provide TypeConverter object for types used to display in DynamicallyAutoDetectDataTypeColumn.
[c#]
[TypeConverter(typeof(DataIntConverter))]
public class DataInt : Data
{
  public DataIntConverter : TypeConverter
  {
    //....
  }
  //.....
}


TypeConverter should provide data conversion from/to string.


Conversion to string of what? Of the whole DataInt object? But I don't need that. I need to make editable a single property of DataInt.

In this case, value of which type must return ConvertFrom() method of the TypeConverter (DataInt or int)?

What to do if I have 2 or more DynamicallyAutoDetectDataTypeColumn-s in the grid? Should I convert to the DataInt type each time the value of one of the properties passed to the ConvertFrom()? But this information is not always enough and DataInt objects can be non-copyable, what to do in this case? Return reference to the grid row data object taken from the context?
What should ConvertFrom() return?
Link Posted: 06-Jul-2006 10:00

Conversion to string of what? Of the whole DataInt object? But I don't need that. I need to make editable a single property of DataInt.

Well, but why you have no overriden DataInt.ToString() method? Problem in that when column trying to convert cell's data to string it tries to find TypeConverter. In case with your type column receives base converter, that converts data to it's type.
It will more correct if you will attach to abstract class universal converter:
[c#]
[TypeConverter(typeof(Data.DataConverter))]
public abstract class Data
{          
  public class DataConverter : TypeConverter
  {
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
      return sourceType == typeof(string) ? true : base.CanConvertFrom (context, sourceType);
    }
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
      return destinationType == typeof(string) ? true : base.CanConvertTo (context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
      if (context != null && value is string)
      {
        //get current value
        Data data = context.Instance as Data;
        //parse string
        if (data != null)
          data.Parse(value as string);
        //return modified data
        return data;
      }
      return base.ConvertFrom (context, culture, value);
    }
    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
      if (context != null && destinationType == typeof(string))
      {
        //get current value
        Data data = context.Instance as Data;
        return data == null || data.Value == null ? string.Empty : data.Value.ToString();
      }
      return base.ConvertTo (context, culture, value, destinationType);
    }
  }

  private string _ID;
  public string DisplayName
  {
    get{ return _ID; }
    set{ _ID = value; }
  }

  protected object _Value;
  public virtual object Value
  {
    get{ return _Value; }
    set{ _Value = value; }
  }
  //new method that helps type converter to convert value from string
  public abstract void Parse(string value);
  
  public Data()
  {
    _ID = \"Name\" + GetNextId();
    _Value = null;
  }

  public static int GetNextId()
  {
    return CurId++;
  }

  private static int CurId;

  static Data()
  {
    CurId = 1;
  }
}

public class DataInt : Data
{
  public DataInt()
  {
    _Value = GetNextId();
  }

  public override void Parse(string value)
  {
      this.Value = int.Parse(value);
  }

}

public class DataString : Data
{
  public DataString()
  {
    _Value = \"s\"+GetNextId().ToString();
  }

  public override void Parse(string value)
  {
    //may be here you need some validation
    this.Value = value;
  }
}