Just stumbled upon propertyGrid and its awesome! However, i have one task that i cant find how to do with it:
I have a class which has a type. Based on type, it has different properties available. I keep it in one class (not multiple inherited classes) for simplicity sake (there are ten types but they mostly have the same properties).
For example, i have a class MapObject which can have string type equal "player" or "enemy". For "enemy", property "name" is used, but for "player" it is blank (not used).
When i select two objects, one of which is of type "player" and other of type "enemy", i want property "name" to only "count" for the "enemy". So, i want propertyGrid to show the name of the object that has type="enemy", and when it (name property) is changed in Grid, only assign it to the object of type "enemy".
Is this possible to do?
Depending on whose PropertyGrid you are using, toggling the Browsable attribute may do what you want. See my answer here for how to do that at runtime.
If, like me, you are using the Xceed PropertyGrid then only changing the Browsable attribute at runtime once the control has loaded doesn't do anything. Instead, I also had to modify the PropertyDefinitions collection. Here is an extension method for doing that:
/// <summary>
/// Show (or hide) a property within the property grid.
/// Note: if you want to initially hide the property, it may be
/// easiest to set the Browable attribute of the property to false.
/// </summary>
/// <param name="pg">The PropertyGrid on which to operate</param>
/// <param name="property">The name of the property to show or hide</param>
/// <param name="show">Set to true to show and false to hide</param>
public static void ShowProperty(this PropertyGrid pg, string property, bool show)
{
int foundAt = -1;
for (int i=0; i < pg.PropertyDefinitions.Count; ++i)
{
var prop = pg.PropertyDefinitions[i];
if (prop.Name == property)
{
foundAt = i;
break;
}
}
if (foundAt == -1)
{
if (show)
{
pg.PropertyDefinitions.Add(
new Xceed.Wpf.Toolkit.PropertyGrid.PropertyDefinition()
{
Name = property,
}
);
}
}
else
{
if (!show)
{
pg.PropertyDefinitions.RemoveAt(foundAt);
}
}
}
In case the above does not work for you, the following may work better and is simpler anyway. It also doesn't use deprecated properties like the code above did...
public static void ShowProperty(this PropertyGrid pg, string property, bool show)
{
for (int i = 0; i < pg.Properties.Count; ++i)
{
PropertyItem prop = pg.Properties[i] as PropertyItem;
if (prop.PropertyName == property)
{
prop.Visibility = show ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
break;
}
}
}
This is a design pattern known as the state pattern. It is pretty easy to implement and you do not need property grids. http://www.dofactory.com/Patterns/PatternState.aspx
I made it with my own custom attribute:
public class VisibilityAttribute : Attribute
{
public bool IsVisible { get; set; }
public VisibilityAttribute(bool isVisible)
{
IsVisible = isVisible;
}
}
Then my data model:
public abstract class BaseSettings: INotifyPropertyChanged
{
public void SetVisibilityProperty(string propertyName, bool isVisible)
{
var theDescriptor = TypeDescriptor.GetProperties(this.GetType())[propertyName];
var theDescriptorVisibilityAttribute = (VisibilityAttribute)theDescriptor.Attributes[typeof(VisibilityAttribute)];
if (theDescriptorVisibilityAttribute == null) return;
theDescriptorVisibilityAttribute.IsVisible = isVisible;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class ThrottleSettings : BaseSettings
{
private ThrottleType _throttleType = ThrottleType.SmartThrottle;
[PropertyOrder(1)]
public ThrottleType ThrottleType
{
get
{
return _throttleType;
}
set
{
if (value == ThrottleType.FullThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), true);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), false);
}
else if (value == ThrottleType.SmartThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), false);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), true);
}
else if (value == ThrottleType.NoThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), false);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), false);
}
_throttleType = value;
}
}
private int _fullThrottleTime = 100;
[PropertyOrder(2)]
[Visibility(false)]
[Description("Specifies full throttle time (in milliseconds).")]
public int FullThrottleTimeInMilliseconds
{
get
{
return _fullThrottleTime;
}
set
{
if (value < 0) return;
_fullThrottleTime = value;
}
}
private int _thumbnailThrottleTime = 0;
[PropertyOrder(3)]
[Visibility(true)]
[Description("Specifies thumbnail throttle time (in milliseconds).")]
public int ThumbnailThrottleTimeInMilliseconds
{
get
{
return _thumbnailThrottleTime;
}
set
{
if (value < 0) return;
_thumbnailThrottleTime = value;
}
}
}
Finally I subscribed to 'propertyGrid_PropertyValueChanged' event and call there my method:
private void _propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
RefreshVisibility(_propertyGrid.Properties);
}
Here is method itself:
private void RefreshVisibility(IList properties)
{
foreach (PropertyItem property in properties)
{
var visibilityAttribute = GetVisibilityAttribute(property);
if (visibilityAttribute != null)
{
if (visibilityAttribute.IsVisible)
property.Visibility = Visibility.Visible;
else
property.Visibility = Visibility.Collapsed;
}
RefreshVisibility(property.Properties);
}
}
Related
I have a custom form where I have a GridView on it. Most of my forms will inherit from this custom form.
so let's say that I have
class A : B
{
//Contents
}
with the above scenario, my problem is: I am not able to edit the grid's columns,
on the designer view's property grid. it's like they are locked.
so I have decided to create a custom property to set a list of column names etc.
so to do this I have these classes
[TypeConverter(typeof(BrowseLayoutColumns))]
public class BrowseLayoutColumns : ExpandableObjectConverter
{
#region Properties
private string _columnName = string.Empty;
public string ColumnName
{
get => _columnName;
set
{
if (null == value) return;
_columnName = value;
}
}
private string _bindingField = string.Empty;
public string BindingField
{
get => _bindingField;
set
{
if (null == value) return;
_bindingField = value;
}
}
#endregion
public override string ToString()
{
return "Columns";
}
}
internal class MyList<T> : List<T> where T : class
{
#region ListMethods
public new void Add(T item)
{
base.Add(item);
ListChanged?.Invoke();
}
public new void Clear()
{
base.Clear();
ListChanged?.Invoke();
}
#endregion
#region Events
public event ListChangedEventHandler ListChanged;
public delegate void ListChangedEventHandler();
#endregion
}
and inside my Custom class I added
private MyList<BrowseLayoutColumns> _browseLayoutColumns = new MyList<BrowseLayoutColumns>();
[Category("Design")]
public MyList<BrowseLayoutColumns> BrowseLayoutColumns
{
get => _browseLayoutColumns;
set => _browseLayoutColumns = value;
}
and inside form Initialization I've created the ListChanged event.
private void _browseLayoutColumns_ListChanged()
{
if (_browseLayoutColumns == null) return;
foreach (var column in _browseLayoutColumns)
{
myGridView1.Columns.Add(column.ColumnName, column.BindingField);
}
}
so now as you can see below in the design time I'm able to add columns
the problem here is, it's like the data entered here is not persistent, I mean, it is not adding these values to the columns because my event is not triggered when I run the program and when I debug I see that my BrowseLayoutList property is empty.
any help?
P.S I've tested my event and others by adding to browselayoutcolumns property manually
I have a class CustomControl which inherits from System.Windows.Forms.Control.
I will also create a new class named GraphicsData which will have all the graphical information about my CustomControl (I need this because it's easier to serialize the data for saving it in a DB, json, etc.)
The CustomControl object will get the GraphicsData at the initialization(in the constructor) and I want it to get all the properties that have a value in GraphicsData (sometimes I don't want to initialize all of the properties from GraphicsData and I want them to remain the default from System.Windows.Forms.Control class).
The problem is that most of the proprierties are not nullable and I cannot check if they are null so I can't do a simple:
customControl.BackColor = graphicsData.BackColor.HasValue ? graphicsData.BackColor.Value : BackColor;
I can of course work this around if I create my own Nullable class but this got really ugly and hard to understand the code. Also, it is very hard to add a new property when needed.
Now, what I did and I think this is a much cleaner way is the following:
GraphicsData class:
public class GraphicsData : INotifyPropertyChanged
{
private readonly List<string> _initializedProperties = new List<string>();
public List<string> InitializedProperties { get { return _initializedProperties; } }
public event PropertyChangedEventHandler PropertyChanged;
private Size _size;
private Point _location;
private AnchorStyles _anchor;
private Color _backColor;
private Image _backgroundImage;
private Cursor _cursor;
private Font _font;
private Color _foreColor;
private bool _enabled;
private bool _visible;
public Size Size
{
get { return _size; }
set
{
_size = value;
OnPropertyChanged("Size");
}
}
public Point Location
{
get { return _location; }
set
{
_location = value;
OnPropertyChanged("Location");
}
}
public AnchorStyles Anchor
{
get { return _anchor; }
set
{
_anchor = value;
OnPropertyChanged("Anchor");
}
}
public Color BackColor
{
get { return _backColor; }
set
{
_backColor = value;
OnPropertyChanged("BackColor");
}
}
public Image BackgroundImage
{
get { return _backgroundImage; }
set
{
_backgroundImage = value;
OnPropertyChanged("BackgroundImage");
}
}
public Cursor Cursor
{
get { return _cursor; }
set
{
_cursor = value;
OnPropertyChanged("Cursor");
}
}
public Font Font
{
get { return _font; }
set
{
_font = value;
OnPropertyChanged("Font");
}
}
public Color ForeColor
{
get { return _foreColor; }
set
{
_foreColor = value;
OnPropertyChanged("ForeColor");
}
}
public bool Enabled
{
get { return _enabled; }
set
{
_enabled = value;
OnPropertyChanged("Enabled");
}
}
public bool Visible
{
get { return _visible; }
set
{
_visible = value;
OnPropertyChanged("Visible");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (!_initializedProperties.Contains(propertyName))
_initializedProperties.Add(propertyName);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And in my custom control I have a method:
public void LoadGraphics()
{
var initializedProperties = graphics.InitializedProperties;
foreach (string propertyName in initializedProperties)
{
var value = graphics.GetType()
.GetProperty(propertyName)
.GetValue(graphics, null);
_customControl.GetType()
.GetProperty(propertyName)
.SetValue(_customControl, value, null);
}
}
Basically, I created a List named InitializedProperties and in the properties "set" I add the property in the list.
After that, using reflection in my CustomControl, I can load all the initialized properties.
I implemented the INotifyPropertyChanged because I also want to change the customControl properties each time a property is changed in GraphicsData.
Is this the proper way to do what I want ? I don't think the reflection code is that readable and I am concerned about the performance.
Using nullable values is a much easier method for achieving this.
C# already has a built-in Nullable class, but it also offers a simple way to make a value nullable without the excess verbosity that the Nullable class introduces: ?.
All of your values can be made nullable by appending the ? operator to the value types:
private Size? _size;
private Point? _location;
private AnchorStyles? _anchor;
private Color? _backColor;
private Image _backgroundImage;
private Cursor _cursor;
private Font _font;
private Color? _foreColor;
private bool? _enabled;
private bool? _visible;
Your LoadGraphics method can easily check to see if the GraphicsData property has a non-null value, and set the corresponding control property if so.
public void LoadGraphics(GraphicsData gfx)
{
// It may be permissible to utilize a null value for BackgroundImage!
// In this case, utilizing a separate field (IsBackgroundImageSet) may be a necessary
if (gfx.BackgroundImage != null) { _customControl.BackgroundImage = gfx.BackgroundImage; }
if (gfx.Size != null) { _customControl.Size = gfx.Size.Value; }
if (gfx.Location != null) { _customControl.Location = gfx.Location.Value }
if (gfx.Anchor != null) { _customControl.Anchor = gfx.Anchor.Value; }
if (gfx.BackColor != null) { _customControl.BackColor = gfx.BackColor .Value; }
if (gfx.Cursor != null) { _customControl.Cursor = gfx.Cursor; }
if (gfx.Font != null) { _customControl.Font = gfx.Font; }
if (gfx.Color != null) { _customControl.Color = gfx.Color.Value; }
if (gfx.Enabled != null) { _customControl.Enabled = gfx.Enabled.Value; }
if (gfx.Visible != null) { _customControl.Visible = gfx.Visible.Value; }
}
I'm developing a WPF application with MVVM Light Toolkit. I just want to display a nested Observablecollection which hold the Employee Attendance details into a DataGrid and do some CRUD functionality in the inner grid and based on those changes I have to automatically recalculate the Outer collection record. The inner collection (PunchDetailModels) is showing in the RowDetailsTemplate of the DataGrid.
Here is the Models :
public class AttendanceModel : ObservableObject
{
public const string EmpNamePropertyName = "EmpName";
private string _empName = string.Empty;
public string EmpName
{
get
{
return _empName;
}
set
{
Set(EmpNamePropertyName, ref _empName, value);
}
}
public const string PunchDetailModelsPropertyName = "PunchDetailModels";
private ObservableCollection<PunchDetailModel> _punchDetailModels = null;
public ObservableCollection<PunchDetailModel> PunchDetailModels
{
get
{
return _punchDetailModels;
}
set
{
Set(PunchDetailModelsPropertyName, ref _punchDetailModels, value);
}
}
private string _inOutCount;
public string InOutCount
{
get
{
return PunchDetailModels != null
? string.Format("{0}/{1}", PunchDetailModels.Count(i => i.PunchStatus == Enums.PunchType.CheckIn),
PunchDetailModels.Count(i => i.PunchStatus == Enums.PunchType.CheckOut))
: null;
}
}
public TimeSpan? FirstCheckIn
{
get
{
if (_punchDetailModels != null)
{
var firstCheckIn =
_punchDetailModels.OrderBy(t => t.PunchTime)
.FirstOrDefault(i => i.PunchStatus == Enums.PunchType.CheckIn);
if (firstCheckIn != null)
return firstCheckIn.PunchTime;
}
return null;
}
}
public TimeSpan? LastCheckOut
{
get
{
if (_punchDetailModels != null)
{
var lastCheckOut =
_punchDetailModels.OrderBy(t => t.PunchTime)
.LastOrDefault(o => o.PunchStatus == Enums.PunchType.CheckOut);
if (lastCheckOut != null)
return lastCheckOut.PunchTime;
}
return null;
}
}
public TimeSpan? TotalInTime
{
get
{
TimeSpan totalInTime = TimeSpan.Zero;
if (_punchDetailModels != null)
{
if (!IsValidRecord()) return null;
for (int inTime = 0; inTime < _punchDetailModels.Count; inTime += 2)
{
totalInTime += _punchDetailModels[inTime + 1].PunchTime - _punchDetailModels[inTime].PunchTime;
}
}
return totalInTime;
}
}
public TimeSpan? TotalOutTime
{
get
{
TimeSpan totalInTime = TimeSpan.Zero;
if (_punchDetailModels != null)
{
if (!IsValidRecord()) return null;
for (int inTime = 1; inTime < _punchDetailModels.Count - 1; inTime += 2)
{
totalInTime += _punchDetailModels[inTime + 1].PunchTime - _punchDetailModels[inTime].PunchTime;
}
}
return totalInTime;
}
}
}
public class PunchDetailModel : ObservableObject
{
public const string PunchStatusPropertyName = "PunchStatus";
private Enums.PunchType _punchStatus;
public Enums.PunchType PunchStatus
{
get
{
return _punchStatus;
}
set
{
Set(PunchStatusPropertyName, ref _punchStatus, value);
}
}
public const string PunchTimePropertyName = "PunchTime";
private TimeSpan _punchTime = TimeSpan.Zero;
public TimeSpan PunchTime
{
get
{
return _punchTime;
}
set
{
Set(PunchTimePropertyName, ref _punchTime, value);
}
}
}
ViewModel :
public const string AttendanceCollectionPropertyName = "AttendanceCollection";
private ObservableCollection<AttendanceModel> _attendanceCollection = null;
public ObservableCollection<AttendanceModel> AttendanceCollection
{
get
{
if (_attendanceCollection == null)
{
_attendanceCollection = new ObservableCollection<AttendanceModel>();
//_attendanceCollection.CollectionChanged+=_attendanceCollection_CollectionChanged;
}
return _attendanceCollection;
}
set
{
Set(AttendanceCollectionPropertyName, ref _attendanceCollection, value);
}
}
View :
Issues I'm facing :
1) When a user ADD or DELETE a particular record from Inner DataGrid, I need to get notification in the View Model. I know it's possible by registering a collection changed event for an ObservableCollection. But how it's possible for an inner ObservableCollection ?
2) I need to get notifications in the viewmodel for any change in CheckIn or Checkout field in the Inner DataGrid, so that I can recalucate fields like TotalInTime, TotalOutTime etc.
How can I do this ? I'm currently stuck with this scenario. Please suggest your valuable points.
I'm guessing that the ObservableObject class is your own implementation of INotifyPropertyChanged interface. Now to solve your issues:
You should register to CollectionChanged event in _punchDetailModels and raise a PropertyChanged event for that variable in the handler, like so:
public ObservableCollection<PunchDetailModel> PunchDetailModels
{
get
{
return _punchDetailModels;
}
set
{
Set(PunchDetailModelsPropertyName, ref _punchDetailModels, value);
_punchDetailModels.CollectionChanged += handler;
}
}
private void handler(object sender, NotifyCollectionChangedEventArgs e)
{
base.RaisePropertyChanged(PunchDetailModelsPropertyName); // If you don't have a method with such signature in ObservableObject (one that takes a string and raises PropertyChanged for it) you'll have to write it.
}
This way the view should reload automatically when adding or removing elements from the inner collection.
There is no other way than to subscribe to listen to PropertyChanged on these fields. That's what the View does and that's what the ViewModel should do also. Like so:
public const string AttendanceCollectionPropertyName = "AttendanceCollection";
private ObservableCollection<AttendanceModel> _attendanceCollection = null;
public ObservableCollection<AttendanceModel> AttendanceCollection
{
get
{
if (_attendanceCollection == null)
{
_attendanceCollection = new ObservableCollection<AttendanceModel>();
}
return _attendanceCollection;
}
set
{
Set(AttendanceCollectionPropertyName, ref _attendanceCollection, value);
_attendanceCollection.CollectionChanged+= handler
}
}
private void handler(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (AttendanceModel model in AttendanceCollection)
model.PropertyChanged += somethingChanged;
}
// Very ineffective to subscribe to all elements every time a list changes but I leave optimization to you.
private somethingChanged (object obj, PropertyChangedEventArgs args)
{
if ( args.PropertyName == "CheckIn" ) // for example
{
AttendanceModel ModelToRecalculate = obj as AttendanceModel;
// You can do anything you want on that model.
}
}
And of course you need to raise PropertyChanged with string argument of value CheckIn in the AttendanceModel class when You think it's necessary ( for example in the handler method)
EDIT:
To answer your comment question:
"Come to second one - I need to recalculate the Attendance Model properties like InOutCount, TotalInTime, TotalOutTime on PunchTime field update."
The answer is: You don't need to do anything in the ViewModel to "recalculate". The UI is subscribed to PropertyChangefor InOutCount , FirstCheckIn ... and so on. It's beacause of Binding (it does it automatically).
So All you need to do to inform the UI that given model needs to be recalculated is call RaisePropertyChanged("InOutCount"), RaisePropertyChanged("FirstCheckIn").
The UI will understand that it needs to GET these properties and because you have these calcualations in property getters, it'll get recalculated.
So, I see that UI needs to be recalculated every time that the INNER list changes, so all you need to do is change the handler code to CollectionChanged for PunchDetailModels like this:
// the handler for CollectionChanged for the INNER collection (PunchDetailModels)
private void handler(object sender, NotifyCollectionChangedEventArgs e)
{
base.RaisePropertyChanged(PunchDetailModelsPropertyName); // If you don't have a method with such signature in ObservableObject (one that takes a string and raises PropertyChanged for it) you'll have to write it.
base.RaisePropertyChanged("InOutCount")
base.RaisePropertyChanged("FirstCheckIn")
base.RaisePropertyChanged("LastCheckOut")
// and so on for all the properties that need to be refreshed
}
If I had an Observable collection like so :
public ObservableCollection<SpecialPriceRow> SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
SpecialPriceRow class :
public class SpecialPriceRow : INotifyPropertyChanged
{
public enum ChangeStatus
{
Original,
Added,
ToDelete,
Edited
}
public ChangeStatus Status { get; set; }
public string PartNo { get; set; }
private decimal _price;
public decimal Price
{
get
{
return _price;
}
set
{
if (value != _price)
{
_price = value;
Status = ChangeStatus.Edited;
OnPropertyChanged("Status");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Would it be possible for me to bind a Label in the XAML to the count of objects that are say ... Added? So I could get something like this :
Where green is the count of "Added" objects within the collection. How would I go about doing something like this?
I've written up a ViewModel which will perform the desired functionality you are looking for.
class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<SpecialPriceRow> _SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
private int _Original = 0;
private int _Added = 0;
private int _ToDelete = 0;
private int _Edited = 0;
public VM()
{
PropertyChanged = new PropertyChangedEventHandler(VM_PropertyChanged);
//The following lines in the constructor just initialize the SpecialPriceRows.
//The important thing to take away from this is setting the PropertyChangedEventHandler to point to the UpdateStatuses() function.
for (int i = 0; i < 12; i++)
{
SpecialPriceRow s = new SpecialPriceRow();
s.PropertyChanged += new PropertyChangedEventHandler(SpecialPriceRow_PropertyChanged);
SpecialPriceRows.Add(s);
}
for (int j = 0; j < 12; j+=2)
SpecialPriceRows[j].Price = 20;
}
private void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private void SpecialPriceRow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Status")
UpdateStatuses();
}
public ObservableCollection<SpecialPriceRow> SpecialPriceRows
{
get
{
return _SpecialPriceRows;
}
}
private void UpdateStatuses()
{
int original = 0, added = 0, todelete = 0, edited = 0;
foreach (SpecialPriceRow SPR in SpecialPriceRows)
{
switch (SPR.Status)
{
case SpecialPriceRow.ChangeStatus.Original:
original++;
break;
case SpecialPriceRow.ChangeStatus.Added:
added++;
break;
case SpecialPriceRow.ChangeStatus.ToDelete:
todelete++;
break;
case SpecialPriceRow.ChangeStatus.Edited:
edited++;
break;
default:
break;
}
}
Original = original;
Added = added;
ToDelete = todelete;
Edited = edited;
}
public int Original
{
get
{
return _Original;
}
set
{
_Original = value;
PropertyChanged(this, new PropertyChangedEventArgs("Original"));
}
}
public int Added
{
get
{
return _Added;
}
set
{
_Added = value;
PropertyChanged(this, new PropertyChangedEventArgs("Added"));
}
}
public int ToDelete
{
get
{
return _ToDelete;
}
set
{
_ToDelete = value;
PropertyChanged(this, new PropertyChangedEventArgs("ToDelete"));
}
}
public int Edited
{
get
{
return _Edited;
}
set
{
_Edited = value;
PropertyChanged(this, new PropertyChangedEventArgs("Edited"));
}
}
}
Take note of the comments in the constructor. You need to point the PropertyChanged event of each SpecialPriceRow to the UpdateStatuses function in order for this to work properly.
Now all you need to do is bind your labels to the appropriate properties in the ViewModel.
If your SpecialPriceRows list becomes very large, you may want to consider calculating the status counts a bit differently. Currently, it is iterating through the entire list every time one instance is updated. For this to perform better, you may want to keep the old value of the status in the SpecialPriceRow class and every time an update occurs, increment the new status count and decrement the old one.
I'm not aware of any builtin functionality to do this. I would create a custom property in your data context class that does the counting and bind to this.
Something like this:
public int AddedCount
{
get
{
return SpecialPriceRows.Where(r => r.Status == ChangeStatus.Added).Count();
}
}
Then whenever an item is changed or added you need to explicitly raise the property changed for this:
public void AddItem()
{
...
OnPropertyChanged("AddedCount");
}
Then you only need to bind in your XAML code like:
<TextBlock Text="{Binding AddedCount}" />
You may need to subscribe to the change events in your collection to know when an item changes.
Alternative:
You can also create a ListCollectionView with a specific filter and bind to its Count property:
AddedItems = new ListCollectionView(TestItems);
AddedItems.Filter = r => ((SpecialPriceRow)r).Status == ChangeStatus.Added;
In your XAML you would then bind to the Count property of this:
<TextBlock Text="{Binding AddedItems.Count}" />
This has the advantage that it will automatically keep track of added and removed items in the collection and update itself. You have to refresh it manually though when the property of an item changes which affects the filter.
I need some help here.
I've created a child class called MyEditorRow from DevExpress EditorRow, and added 3 properties
public class myEditorRow : EditorRow
{
public myEditorRow()
{
}
private string inRowDescription = null;
public string RowDescription
{
get { return inRowDescription; }
set { inRowDescription = value; }
}
private bool inRequired = false;
public bool Required
{
get { return inRequired; }
set { inRequired = value; }
}
private bool inInherits = false;
public bool Inherits
{
get { return inInherits; }
set { inInherits = value; }
}
Second part of the code somewhere in the program adds instance of MyEditorRow to DevExpress VGrid Control.
vgcGrid.Rows.Add(Row);
My question is this: How can I link MyEditorRow class with DevExpress VGrid Control FocusedRowChanged event, so I can get my custom properties when row focus changes.
Thanks
The e.Row parameter is of the BaseRow type. So, to obtain an instance of the MyEditorRow object in the FocusnedRowChanged event handler, use the following code:
private void vGridControl1_FocusedRowChanged(object sender, DevExpress.XtraVerticalGrid.Events.FocusedRowChangedEventArgs e) {
if(e.Row is myEditorRow) {
myEditorRow row = ((myEditorRow)e.Row);
// your code here
}
}