set not being called when editing a collection - c#

I have a class containing a collection property which I want to display and edit in a property grid:
[EditorAttribute(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
public List<SomeType> Textures
{
get
{
return m_collection;
}
set
{
m_collection = value;
}
}
However, when I try to edit this collection with the CollectionEditor, set is never called; why is this and how can I fix it?
I also tried to wrap my List<SomeType> in my own collection as described here:
http://www.codeproject.com/KB/tabs/propertygridcollection.aspx
But neither Add, nor Remove is being called when I add and remove items in the CollectionEditor.

Your setter isn't being called because when you're editting a collection, you're really getting a reference to the original collection and then editting it.
Using your example code, this would only call the getter and then modify the existing collection (never resetting it):
var yourClass = new YourClass();
var textures = yourClass.Textures
var textures.Add(new SomeType());
To call the setter, you would actually have to assign a new collection to the Property:
var yourClass = new YourClass();
var newTextures = new List<SomeType>();
var newTextures.Add(new SomeType());
yourClass.Textures = newTextures;

Related

ObservableCollection<> not loading properly

I am trying to implement filtering on an ObservableCollection<>. My current ObservableCollection<Payee> is working fine as an ItemsSource on a GridView. I added a second ObservableCollection<Payee> called FilteredPayees to use as the ItemsSource. For some reason, when I try to filter the items, the GridView is showing up blank.
Here is the code I'm using:
private void FilterPayees()
{
if (!_settings.ShowInactivePayees)
{
var filtered = _payees.Where(p => p.IsOpen == true);
_filteredPayees = new ObservableCollection<Payee>(filtered);
}
else
{
_filteredPayees = _payees;
}
this.FilteredPayees = _filteredPayees;
}
Basically, if the ShowInactivePayees setting is turned off, it should filter out the inactive payees. If it is on, then just use the full _payees collection. The strange thing, if I change the last line to:
this.FilteredPayees = _payees;
then the GridView will display all of the payees, just as it should if the "show inactive payees" settings is turned on. I set breakpoints and the _filteredPayees collection has 35 items in it (or 65 when not filtering). It does not appear to be any type of "object not set to an instance of an object" or anything like that. Is there some reason that
this.FilteredPayees = _payees;
would work, but
_filteredPayees = _payees;
this.FilteredPayees = _filteredPayees;
would not?
EDIT
I was able to get it to work for now by getting rid of the FilteredPayees property. I just filter the original Payees collection in the OnNavigatedTo() event handler, which is exactly the same place where I was calling FilteredPayees().
// load payees
var payees = await _payeesRepository.LoadAllAsync();
if (!_settings.ShowInactivePayees)
{
payees = payees.Where(p => p.IsOpen);
}
payees = payees.OrderBy(p => p.CompanyName);
this.Payees = new ObservableCollection<Payee>(payees);
The only part I added was the if (!_settings.ShowInactivePayees) ... block. My reasoning to use the FilteredPayees property was so that I could have the full collection loaded in the Payees property and not need to reload if the ShowInactivePayees setting was changed - just change the filter of the collection.
You are assigning a new object to FilteredPayees property, so GridView has to be notified that the property FilteredPayees is changed. There should be RaisePropertyChanged("FilteredPayees") or your notification code in the setter of FilteredPayees.
Also, the binding mode of GridView.ItemsSource should not be BindingMode.OneTime.
For filtering a collection in WPF it may be easier to use ICollectionView. For example:
public class Foo
{
private List<Payee> _payees;
private ICollectionView _filteredPayees;
public ICollectionView FilteredPayees
{
get { return _filteredPayees; }
}
public Foo()
{
_payees = GetPayees();
_filteredPayees = CollectionViewSource.GetDefaultView(_payees);
_filteredPayees.Filter = FilterPayees;
}
private bool FilterPayees(object item)
{
var payee = item as Payee;
if (payee == null)
{
return false;
}
if (_settings.ShowInactivePayees)
{
return true;
}
return payee.IsOpen;
}
}
You can bind the property FilteredPayees like any other property. The advantage is, that you don't need two properties and you can avoid the logic of which collection you want to bind.
_filteredPayees = new ObservableCollection<Payee>(filtered);
Here you create a completely new object, and that's not something ObservableCollection can automatically observe. The possible solution is to set ItemsSource on your GridView again after this line.

WPF ListView: Changing ItemsSource does not change ListView

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.
After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();
This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.
You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )
An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.
Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />

Designer serialization and collection

I have an custom control that represents a grid; and implements another custom control.
When opening this control in de designer, I am able to use the collection editor to set my collection. When saving; the designer successfully saves my collection.
However, when dropping this control on a form; it still (and should) expose(s) the collection property allowing me to modify the default values as i have defined in the other control.
However; when saving this designer; it also tries to store the predefined items in the collection; adding the default ones with every save.
What is the best way to solve this problem? I have attached a code sample.
Code sample where i have defined my collection:
GridPicture.cs
[Category("Layout")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public GridPictureColumnDefinitionCollection ColumnDefinitions
{
// The DesignerSerializationVisibility attribute instructs the design editor to serialize the contents of the collection to source code.
// This will place all the code required to add the items to a collection variable of GridPictureColumnDefinitionCollection.
get
{
return m_ColumnDefinitions;
}
}
Generated designer code of my first 'implementation' of this grid; Picture1.cs
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition1 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition2 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureRowDefinition gridPictureRowDefinition1 = new VGTest.GridPictureRowDefinition();
gridPictureColumnDefinition1.Auto = true;
gridPictureColumnDefinition1.Value = 0F;
gridPictureColumnDefinition2.Auto = true;
gridPictureColumnDefinition2.Value = 0F;
this.ColumnDefinitions.Add(gridPictureColumnDefinition1);
this.ColumnDefinitions.Add(gridPictureColumnDefinition2);
gridPictureRowDefinition1.Auto = true;
gridPictureRowDefinition1.Value = 0F;
this.RowDefinitions.Add(gridPictureRowDefinition1);
Code sample when i place this picture1 on another picture; picture2.cs: (Note that picture11 is the picture1, as it is the 1st of picture1 ;)
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition1 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition2 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition3 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureRowDefinition gridPictureRowDefinition1 = new VGTest.GridPictureRowDefinition();
// Some code removed that does the Auto and Value settings as above
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition1);
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition2);
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition3);
The picture2 control; when it regenerates the InitializeComponent() method; now adds the columndefinitions which i have added in picture1.
I have written this temporary fix for the problem:
{
// This makes sure column definitions are only serialized when configured at a implementation of this GridPicture.
// This is a Quick/Dirty fix for the following problem:
// When MyPanel (:GridPicture) is put on PanelContainer(:Picture); the picture designer (re)serializes this each save.
return this.GetType().BaseType.Name == typeof(Picture).Name;
}
I was unable to find a better method. I have decided to stick with the solution below.
{
// This makes sure column definitions are only serialized when configured at a implementation of this GridPicture.
// This is a Quick/Dirty fix for the following problem:
// When MyPanel (:GridPicture) is put on PanelContainer(:Picture); the picture designer (re)serializes this each save.
return this.GetType().BaseType.Name == typeof(Picture).Name;
}

Binding collections to DataGridView in Windows Forms

I'm trying to bind a collection to a DataGridView. As it turns out it's impossible for the user to edit anything in this DataGridView although EditMode is set to EditOnKeystrokeOrF2.
Here is the simplified code:
public Supplies()
{
InitializeComponent();
List<string> l = new <string>();
l.Add("hello");
this.SuppliesDataGridView.DataSource = l;
}
It also doesn't work when I change the collection type to SortableBindingList, Dictionary or even use a BindingSource.
What can be wrong here?
For me the following method works as expected:
Open your form (usercontrol, etc.) with the designer
Add a BindingSource to your form
Select the BindingSource in your form and open the properties page
Select the DataSource property and click on the down arrow
Click on Add project data source
Select Object
Select the object type you wish to handle
This should be the type that will be handled by your collection, not the CustomCollection itself!
Show the available data sources by selecting from the MenuBar Data - Show Data Sources
Drag and Drop your ItemType from the DatasSources on your form
Go into the code of your form and bind your CustomCollection to the BindingSource
var cc = new CustomCollection();
bindingSource1.DataSource = cc;
Remarks:
The DataGridView is just the last part in your chain to (dis)allow changing, adding and removing objects from your list (or CustomCollection). There is also a property AllowNew within the BindingSource and the ICollection interface has a property IsReadOnly which must be set to false to allow editing. Last but not least, the properties of your class within the collection must have a public setter method to allow changing of a value.
Try this:
public class CustomCollection { public string Value { get; set; } }
public Supplies()
{
InitializeComponent();
List<CustomCollection> l = new List<CustomCollection> { new CustomCollection { Value = "hello" } };
this.SuppliesDataGridView.DataSource = l;
}
Once you've set the DataSource property you'll then want to fire off the DataBind() method.
this.SuppliesDataGridView.DataSource = l;
this.SuppliesDataGridView.DataBind();
UPDATE:
As you rightly pointed out in the comments, the DataBind() method doesn't exist for this control.
This link might provide some helpful information: http://msdn.microsoft.com/en-us/library/fbk67b6z%28v=VS.90%29.aspx

FrameworkElementFactory "ignores" parent resources (e.g. styles)

I am trying to create some custom treeviews. Everything is working fine so far, but I got a little problem with styles. I have a simple "RedBackground" Style which I add to the resources of the Window. When adding normal elements, it works fine.
When using a custom item template to render treeview items, my resource is ignored. If I add the resource directly to the template it works fine (as marked in code)...
I obviously do not want to have to add styles to the ItemTemplate direclty, would be very complicated in further development. I think I am missing some kind of "Binding" or "Lookup"... I think it is related to dependency properties... Or something in this direction.
Perhaps anyone has more insights, here is the code creating the template (inside util class, but thats just to keep all clean):
var hdt = new HierarchicalDataTemplate(t)
{
ItemsSource = new Binding("Children")
};
var tb = new FrameworkElementFactory(typeof (TextBlock));
tb.SetBinding(TextBlock.TextProperty, new Binding("Header"));
hdt.VisualTree = tb;
// This way it works...
TextBlockStyles.AddRedBackground(hdt.Resources);
return hdt;
And here my very simple custom tree view
public class TreeViewCustom<T> : TreeView
{
public TreeViewCustom()
{
MinWidth = 300;
MinHeight = 600;
ItemTemplate = TreeViewTemplates.TryGetTemplate(typeof(T));
// This is ignored.... (Also when set as resource to window)
TextBlockStyles.AddRedBackground(Resources);
}
}
Ok, and to be sure, here the code which creates the Style:
public static class TextBlockStyles
{
public static void AddRedBackground(ResourceDictionary r)
{
var s = CreateRedBackground();
r.Add(s.TargetType, s);
}
private static Style CreateRedBackground()
{
var s = new Style(typeof(TextBlock));
s.Setters.Add(new Setter
{
Property = TextBlock.BackgroundProperty,
Value = new SolidColorBrush(Colors.Red)
});
return s;
}
}
Thanks for any tips...
Chris
Is this a problem with "inheritance"? Not all properties are inherited, read more here:
Property Value Inheritance: http://msdn.microsoft.com/en-us/library/ms753197.aspx

Categories

Resources