PropertyGrid control + array of custom objects - arrays

I have a PropertyGrid control to which I bind a container with an array of complex objects:
// Collection
public class ParametersCollection
{
private ParameterObject [] _parameters = null;
[Category("SubReportParams")]
public ParameterObject [] Parameters
{
get { return _parameters; }
set { _parameters = value; }
}
public ParametersCollection()
{
// _parameters initialization here...
}
}
// Complex object
public class ParameterObject
{
private string _name = "";
private string _value = "";
[Category("Information"), DisplayName("Name")]
public string Name
{
get { return _name; }
set { _name = value; }
}
[Category("Information"), DisplayName("Value")]
public string Value
{
get { return _value; }
set { _value = value; }
}
}
Everything works fine, except two cases:
In example, if array _parameters has only 2 items, default array size is 4 and items with indexes 2 and 3 are nulls. PropertyGrid displays these items as empty fields. How to force PropertyGrid to ignore these fields and simply not displaying it?
_parameters variable is an array type, so _parameters items are displayed with their indexes from 0 to n. Is there a posibillity to display them with their names from property ParamObject.Name instead of their indexes from an array?

For the fist question, the easiest way is to add a "fake" property computed from the "real" property. It's not perfect, but you can use various attribute to help:
DisplayName to give the fake property the name of the real property
Browsable(false) instructs the property grid to skip the real property
EditorBrowsable(never) instructs Visual Studio's intellisense to not show this property in external code.
[Browsable(false)]
public ParameterObject[] Parameters
{
get { return _parameters; }
set { _parameters = value; }
}
[Category("SubReportParams"), DisplayName("Parameters")]
[EditorBrowsable(EditorBrowsableState.Never)]
public ParameterObject[] NonEmptyParameters
{
get
{
return _parameters.Where(p => p != null).ToArray();
}
}
For the second question, one simple way is to add a ToString() implementation like this:
public class ParameterObject
{
public override string ToString()
{
return Name;
}
}
Otherwise, you can to add a custom TypeConverter to the class.

Related

ObservableCollection deep cloning

I've implemented deep cloning of ObservableCollection in order to reset items to It's original state in editable Datagrid, via cancel button.
For this I have two collections - one ObservableCollection to bind Datagrid to It, and cloned List to re-initialize ObservableCollection to It's original state when needed.
My code works only first time I hit a cancel button, after that my cloned List has changes in It too.
Provided code is an example (mine is a bit longer), but It's 100% same as mine:
Model, which implements ICloneable:
public class EmployeeModel : ICloneable
{
public object Clone()
{
return MemberwiseClone();
}
public string NAME
{
get { return _name; }
set
{
if (_name != value)
{
CHANGE = true;
_name = value;
}
}
}
private string _name;
public string SURNAME
{
get { return _surname; }
set
{
if (_surname != value)
{
CHANGE = true;
_surname = value;
}
}
}
private string _surname;
///<summary>Property for tracking changes in model</summary>
public bool CHANGE { get; set; }
}
Viewmodel:
public ViewModel() : Base //Implements InotifyPropertyChanged
{
public ViewModel()
{
Task.Run(()=> GetData());
}
public ObservableCollection<EmployeeModel> Employees
{
get { return _employees; }
set { _employees = value; OnPropertyChanged();}
}
private ObservableCollection<EmployeeModel> _employees;
public List<EmployeeModel> Copy_employees
{
get { return _copy_employees; }
set { _copy_employees = value; OnPropertyChanged();}
}
private List<EmployeeModel> _copy_employees;
//Fetch data from DB
private async Task Get_data()
{
//Returns new ObservableCollection of type Employee
Employees = await _procedures.Get_employees();
if (Employees != null) //Now make a deep copy of Collection
{
Copy_employees = new List<EmployeeModel>();
Copy_employees = Employees.Select(s => (EmployeeModel)s.Clone()).ToList();
}
}
//My Command for canceling changes (reseting DataGrid)
//CanExecute happens, when model is changed - tracking via CHANGE property of EmployeeModel
public void Cancel_Execute(object parameter)
{
Employees.Clear(); //Tried with re-initializing too, but same result
foreach (var item in Copy_employees)// Reset binded ObservableCollection with old items
{
Employees.Add(item);
}
//Check if copied List really hasn't got any changes
foreach (EmployeeModel item in Copy_employees)
{
Console.WriteLine("Changes are " + item.CHANGES.ToString());
}
}
}
Output of cancel command:
1.) First time I hit cancel button:
// Changes are False
Every next time:
// Changes are True
So, as I see It from Console, my copied List get's updated when ObservableColection get's updated, even if It's not binded to DataGrid.
And It updates only a property which I changed, so List reflects ObservableCollection items.
How can I keep my original items of List<Employee>, and copy those into binded ObservableCollection anytime ?
When you return values, you do not return them, but write backing item references to the editable collection.
As a result, you have the same instances in both collections.
In the simplest case, when you return them, you also need to clone.
public void Cancel_Execute(object parameter)
{
Employees.Clear(); //Tried with re-initializing too, but same result
foreach (var item in Copy_employees)// Reset binded ObservableCollection with old items
{
Employees.Add((EmployeeModel)item.Clone());
}
//Check if copied List really hasn't got any changes
foreach (EmployeeModel item in Copy_employees)
{
Console.WriteLine("Changes are " + item.CHANGES.ToString());
}
}
Not relevant to the question, but I still advise you to use a slightly more user-friendly interface for cloneable:
public interface ICloneable<T> : ICloneable
{
new T Clone();
}

Exposing custom properties using UI Automation Framework

Given a very basic WinForms custom/user control, using System.Windows.Automation it is possible to manipulate built in properties for the custom control.
This is done like this:
public object GetPropertyValue(int propertyId)
{
if (propertyId == AutomationElementIdentifiers.NameProperty.Id)
{
return "Hello World!";
}
}
What I would like to do is expose custom properties to ui automation such as ReadyState, LastAccessed, Etc.
Is this possible?
No, you can't extend the list of properties, and this is complicated by the fact you use Winforms that has a poor UI Automation support (it uses IAccessible with bridges etc.).
What you can do though is add some fake objects to the automation tree, for example, here is a sample Winforms UserControl that does it:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
Button button = new Button();
button.Location = new Point(32, 28);
button.Size = new Size(75, 23);
button.Text = "MyButton";
Controls.Add(button);
Label label = new Label();
label.Location = new Point(49, 80);
label.Size = new Size(35, 13);
label.Text = "MyLabel";
Controls.Add(label);
MyCustomProp = "MyCustomValue";
}
public string MyCustomProp { get; set; }
protected override AccessibleObject CreateAccessibilityInstance()
{
return new UserControl1AccessibleObject(this);
}
protected class UserControl1AccessibleObject : ControlAccessibleObject
{
public UserControl1AccessibleObject(UserControl1 ownerControl)
: base(ownerControl)
{
}
public new UserControl1 Owner
{
get
{
return (UserControl1)base.Owner;
}
}
public override int GetChildCount()
{
return 1;
}
public override AccessibleObject GetChild(int index)
{
if (index == 0)
return new ValueAccessibleObject("MyCustomProp", Owner.MyCustomProp);
return base.GetChild(index);
}
}
}
public class ValueAccessibleObject : AccessibleObject
{
private string _name;
private string _value;
public ValueAccessibleObject(string name, string value)
{
_name = name;
_value = value;
}
public override AccessibleRole Role
{
get
{
return AccessibleRole.Text; // activate Value pattern
}
}
// note you need to override with member values, base value cannot always store something
public override string Value { get { return _value; } set { _value = value; } }
public override string Name { get { return _name; } }
}
And this is how it appears in the automation tree (using the inspect.exe tool):
Note this technique also supports writing back to the property because it's based on the ValuePattern.

How to show Drop down control in Property Grid?

I am adding the Property grid control in my project.
I have to show the Drop down box in one field of Property Grid.
Is there any solution to apply this.
You have to declare a type editor for the property in your PropertyGrid and then add to the list of choices. This example creates a Type Converter and then overrides the GetStandardValues() method to provide choices to the drop-down:
private String _formatString = null;
[Category("Display")]
[DisplayName("Format String")]
[Description("Format string governing display of data values.")]
[DefaultValue("")]
[TypeConverter(typeof(FormatStringConverter))]
public String FormatString { get { return _formatString; } set { _formatString = value; } }
public class FormatStringConverter : StringConverter
{
public override Boolean GetStandardValuesSupported(ITypeDescriptorContext context) { return true; }
public override Boolean GetStandardValuesExclusive(ITypeDescriptorContext context) { return true; }
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
List<String> list = new List<String>();
list.Add("");
list.Add("Currency");
list.Add("Scientific Notation");
list.Add("General Number");
list.Add("Number");
list.Add("Percent");
list.Add("Time");
list.Add("Date");
return new StandardValuesCollection(list);
}
}
The key is the property being assigned a Type Converter in the line:
[TypeConverter(typeof(FormatStringConverter))]
That provides you with the opportunity to introduce your own behavior via overrides.
Here's a simpler example, which allows the Enum type of a Property to automatically provide its values to the PropertyGrid drop-down:
public enum SummaryOptions
{
Sum = 1,
Avg,
Max,
Min,
Count,
Formula,
GMean,
StdDev
}
private SummaryOptions _sumType = SummaryOptions.Sum;
[Category("Summary Values Type")]
[DisplayName("Summary Type")]
[Description("The summary option to be used in calculating each value.")]
[DefaultValue(SummaryOptions.Sum)]
public SummaryOptions SumType { get { return _sumType; } set { _sumType = value; } }
By virtue of the fact that the property is an Enum type, those enum values pass through to become the drop-down options automatically.

How do i detect a WPFDataGrid row Data changed?

I have a DataGrid,and need to detect when a user has make changes to a row.I don't want to use CellEditEnding because whenever a row get focus and lost it without any inputs,this event get raised,in the other way i need to bind a bool property to each row that set to true when the row got chgangd.
Use following code as an example, so you know the basic idea of how to trace if an item in your ItemSource had been changed (here only compared to the initial value only).
List<myItem> Items=new List<myItem>(); //your ItemSource
class myItem:ObservableObject //an class implement INotifyPropertyChanged interface
{
string _inititemName;
string _itemName;
bool itemChanged; //here is your indicator
myItem(string name)
{
_inititemName=itemName=name;
}
public string itemName
{
get{return _itemName;}
set
{
_itemName=vlaue;
if (_itemName!=_inititemName)
itemChanged=true;
else
itemChanged=false;
RaisePropertyChanged("itemName"); //or whatever the name of the method is that invoke OnPropertyChanged
}
}
}
Make the properties of your item class set a boolean update flag when they are modified
e.g.
public class MyGridItem
{
public MyGridItem(string Name)
{
this.Name = Name;
Updated = false;
}
public bool Updated {get; private set;}
private string _Name = null;
public string Name
{
get { return _Name; }
set {
if (!_Name.Equals( value ))
{
_Name = value;
Updated = true
}
}
}
}

Binding Custom Object Datagrid Silverlight 4

I create an object Custom as you can see below
public class GridViewModel
{
private List _listRowCust = new List();
public List ListRowCust
{
get { return _listRowCust; }
set { _listRowCust = value; }
}
}
public class RowCustom
{
private List<CellCustom> _listCellCustom = new List<CellCustom>();
public List<CellCustom> ListCellCustom
{
get { return _listCellCustom; }
set { _listCellCustom = value; }
}
public RowCustom() { }
}
I try to bind the custom object on the datagrid object available in silverlight4.
I wish to bind any cell on the datagrid. One line should identify by a row object and each cell by a cellCustom.
I use this code
textColumn = new DataGridTextColumn();
textColumn.Header = "RemainingWork";
textColumn.Binding = new Binding("Cell[0]"); //it's a supposed syntax possibility in fact I have 3 rows with 10 cells
GridElements.Columns.Add(textColumn);
GridElements.ItemsSource = e.GridViewModel.ListRowCust;
I don't find any explanation on How to custom the binding.
Do you have any idea?
Thank you
best regards,
Alexandre
I think you can only bind to public properties.
So CellCustom must have a public property to bind to, if that's the object used for the itemsSource, or data context.

Resources