How to limit a PropertyGrid collection to a List<T> - c#

Okay, I've read a couple of questions regarding the use of the PropertyGrid and collections. But, I'm having a difficult time understanding how/if [TypeConverter] will work for me. I've read the little blurb-age that MSDN puts out there, and frankly, it's a bit lacking to this poor, self-taught programmer.
So, here is what I have. First a collection:
[Serializable]
public List<ModuleData> Modules
{ get { return modules; } }
private List<ModuleData> modules;
The object in the collection:
[Serializable]
internal class ModuleData : IEquatable<ModuleData>
{
// simple data class with public properties
// to display in the propgrid control
}
I have a ListView control that contains items describing both ModuleData objects and BatchData objects. When I select a BatchData item from the ListView, the PropertyGrid, as expected, displays the collection editor. Is there a way to limit the collection editor to any ModuleData items listed in the ListView control only? Ideally I would not want a BatchData item (from the ListView) to be added to a BatchData collection - especially since the collection is not 'typed' for BatchData object types.
If any further code samples are requested, I'll be more than happy to edit some snippets in.
For clarity, ModuleData is a custom class that holds data required to instance a class within a specified assembly. All it contains are fields and public/internal properties. What I would like to do is use the collection editor assembled with the property grid control to add ModuleData objects to the BatchData Module collection. The ModuleData objects that are qualified to be added are listed in the ListView control.
EDIT: Removed the : List<ModuleData> inheritance.
UPDATE: If I am going to create a custom collection editor, does that mean I am building my own custom form/dialog? Then basically providing the propertygrid the information to display my custom collection dialog through attributes and inheritance of an UITypeEditor?

First off I'm a little unsure about why this both inherits (: List<ModuleData>) and wraps (public List<ModuleData> Modules { get { return this; } }) a list - either individually should be fine.
However! To define the types of new objects you can create you need to derive from CollectionEditor and override the NewItemTypes property - and associate this editor with your type. I'm a little bit unclear on what objects you want to be addable, and whether this is the best design. If you want to add existing objects you may need a completely custom editor / uitypeeditor.
With the updated question, it definitely sounds like a job for a custom UITypeEditor; here's a version that uses a drop-down; you can do popups too (see methods on svc):
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Collections;
static class Program
{
static void Main()
{
MyWrapper wrapper = new MyWrapper();
wrapper.Modules.Add(new ModuleData { ModuleId = 123 });
wrapper.Modules.Add(new ModuleData { ModuleId = 456 });
wrapper.Modules.Add(new ModuleData { ModuleId = 789 });
wrapper.Batches.Add(new BatchData(wrapper) { BatchId = 666 });
wrapper.Batches.Add(new BatchData(wrapper) { BatchId = 777 });
PropertyGrid props = new PropertyGrid { Dock = DockStyle.Fill };
ListView view = new ListView { Dock = DockStyle.Left };
foreach (ModuleData mod in wrapper.Modules) {
view.Items.Add(mod.ToString()).Tag = mod;
}
foreach (BatchData bat in wrapper.Batches) {
view.Items.Add(bat.ToString()).Tag = bat;
}
view.SelectedIndexChanged += delegate {
var sel = view.SelectedIndices;
if(sel.Count > 0) {
props.SelectedObject = view.Items[sel[0]].Tag;
}
};
Application.Run(new Form { Controls = { props, view} });
}
}
class MyWrapper
{
private List<ModuleData> modules = new List<ModuleData>();
public List<ModuleData> Modules { get { return modules; } }
private List<BatchData> batches = new List<BatchData>();
public List<BatchData> Batches { get { return batches; } }
}
class ModuleListEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
IWindowsFormsEditorService svc;
IHasModules mods;
IList selectedModules;
if (context == null || (selectedModules = (IList)value) == null ||
(mods = context.Instance as IHasModules) == null
|| (svc = (IWindowsFormsEditorService)
provider.GetService(typeof(IWindowsFormsEditorService))) == null)
{
return value;
}
var available = mods.GetAvailableModules();
CheckedListBox chk = new CheckedListBox();
foreach(object item in available) {
bool selected = selectedModules.Contains(item);
chk.Items.Add(item, selected);
}
chk.ItemCheck += (s, a) =>
{
switch(a.NewValue) {
case CheckState.Checked:
selectedModules.Add(chk.Items[a.Index]);
break;
case CheckState.Unchecked:
selectedModules.Remove(chk.Items[a.Index]);
break;
}
};
svc.DropDownControl(chk);
return value;
}
public override bool IsDropDownResizable {
get {
return true;
}
}
}
interface IHasModules
{
ModuleData[] GetAvailableModules();
}
internal class BatchData : IHasModules {
private MyWrapper wrapper;
public BatchData(MyWrapper wrapper) {
this.wrapper = wrapper;
}
ModuleData[] IHasModules.GetAvailableModules() { return wrapper.Modules.ToArray(); }
[DisplayName("Batch ID")]
public int BatchId { get; set; }
private List<ModuleData> modules = new List<ModuleData>();
[Editor(typeof(ModuleListEditor), typeof(UITypeEditor))]
public List<ModuleData> Modules { get { return modules; } set { modules = value; } }
public override string ToString() {
return "Batch " + BatchId;
}
}
internal class ModuleData {
[DisplayName("Module ID")]
public int ModuleId { get; set; }
public override string ToString() {
return "Module " + ModuleId;
}
}

Related

Xml List Serialization and Node Type Names

Ive come across multiple questions and answers on here but none specific to my situation.
I have a class 'Entity' with multiple classes that extend off of it. I want the serialization to hit the list and understand and use the type of each item for the node name.
Now, I can use what is commented out (define each array item in the main class and define the name of such by using [XmlArrayItem("Subclass1", typeof(subclass1)] but I want to keep all definitions in their subclass and I will be having too many subclasses to define everything in the main entity class...Is there anyway to achieve this?
I have tried using [XmlType(TypeName="...")] for the subclasses and so on but that did not work.
[Serializable]
[XmlInclude(typeof(Subclass1))]
[XmlRoot("Entity")]
public class Entity{
[XmlArray("CausedBy")]
//[XmlArrayItem("Subclass1", typeof(subclass1))]
//[XmlArrayItem("Sublcass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
[XmlInclude(typeof(Subclass2))]
public class Subclass1:Entity{
//Code...
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2:Subclass1{
//Code...
}
Serializing the above code after creating an entity and adding a Subclass1 and Subclass2 to the list 'CausedBy' class results in the following:
<Entity>
<CausedBy>
<Entity ... xsi:type="SubClass1" />
<Entity ... xsi:type="SubClass2" />
</CausedBy>
<Entity>
I would like the output to show:
<Entity>
<CausedBy>
<SubClass1 .../>
<SubClass2 .../>
</CausedBy>
<Entity>
Since I totally failed to read the question to begin with, here's a new answer (it's a bit of a tl;dr, so you can always skip to the end and follow the link):
It isn't possible to get the built in serializer class to work because you don't wish to add the attributes that it needs to be able to operate. Your only option is to seralize the class yourself, however, this need not be as tedious as it sounds; I had a similar issue a few years ago with DataGridView in virtual mode and produced a generic virtualizer that could be used to virtualize the data for display; it used a custom attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class showColumnAttribute : System.Attribute
{
///<summary>Optional display format for column</summary>
public string Format;
///<summary>Optional Header string for column<para>Defaults to propety name</para></summary>
public string Title;
///<summary>Optional column edit flag - defaults to false</summary>
public bool ReadOnly;
///<summary>Optional column width</summary>
public int Width;
///<summary>
///Marks public properties that are to be displayed in columns
///</summary>
public showColumnAttribute()
{
Format = String.Empty;
Title = String.Empty;
ReadOnly = false;
Width = 0;
}
}
And a constructor:
///<summary>
///Extracts the properties of the supplied type that are to be displayed
///<para>The type must be a class or an InvalidOperationException will be thrown</para>
///</summary>
public Virtualiser(Type t)
{
if (!t.IsClass)
throw new InvalidOperationException("Supplied type is not a class");
List<VirtualColumnInfo> definedColumns = new List<VirtualColumnInfo>();
PropertyInfo[] ps = t.GetProperties();
MethodInfo mg, ms;
for (int i = 0; i < ps.Length; i++)
{
Object[] attr = ps[i].GetCustomAttributes(true);
if (attr.Length > 0)
{
foreach (var a in attr)
{
showColumnAttribute ca = a as showColumnAttribute;
if (ca != null)
{
mg = ps[i].GetGetMethod();
if (mg != null)
{
ms = ps[i].GetSetMethod();
definedColumns.Add
(
new VirtualColumnInfo
(
ps[i].Name, ca.Width, ca.ReadOnly, ca.Title == String.Empty ? ps[i].Name : ca.Title,
ca.Format, mg, ms
)
);
}
break;
}
}
}
}
if (definedColumns.Count > 0)
columns = definedColumns.ToArray();
}
This extracts the public properties of the class and supplies marked items to the DataGridView as columns together with a header, format, etc.
The effect of all of this (and the rest of the missing code) was that any type could be virtualized in a dataGridView simply by tagging public properties and calling the virtualizer once for a given type:
#region Virtualisation
static readonly Virtualiser Virtual = new Virtualiser(typeof(UserRecord));
[XmlIgnore] // just in case!
public static int ColumnCount { get { return Virtual.ColumnCount; } }
public static VirtualColumnInfo ColumnInfo(int column)
{
return Virtual.ColumnInfo(column);
}
public Object GetItem(int column)
{
return Virtual.GetItem(column, this);
}
/*
** The supplied item should be a string - it is up to this method to supply a valid value to the property
** setter (this is the simplest place to determine what this is and how it can be derived from a string).
*/
public void SetItem(int column, Object item)
{
String v = item as String;
int t = 0;
if (v == null)
return;
switch (Virtual.GetColumnPropertyName(column))
{
case "DisplayNumber":
if (!int.TryParse(v, out t))
t = 0;
item = t;
break;
}
try
{
Virtual.SetItem(column, this, item);
}
catch { }
}
#endregion
The number of columns, their properties and order can be specified automatically by creating a number of public properties derived from the class data:
#region Display columns
[showColumn(ReadOnly = true, Width = 100, Title = "Identification")]
public String DisplayIdent
{
get
{
return ident;
}
set
{
ident = value;
}
}
[showColumn(Width = 70, Title = "Number on Roll")]
public int DisplayNumber
{
get
{
return number;
}
set
{
number = value;
}
}
[showColumn(Width = -100, Title = "Name")]
public string DisplayName
{
get
{
return name == String.Empty ? "??" : name;
}
set
{
name = value;
}
}
#endregion
This would virtualize any class for dataGridView to display and edit data and I used it many times over the years and the extraction of properties to display is exactly what is required for XML serialization, indeed, it has a lot of the same characteristics.
I was going to adapt this method to do the same job for XML serialization but someone has already done it at https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=474453, I hope you can make use of this method to solve your problem.
This works for me:
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Entity entity = new Entity();
entity.CausedBy = new List<Entity>();
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass1());
entity.Save(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Test.txt"));
}
}
[Serializable]
[XmlRoot("Entity")]
public class Entity
{
[XmlArray("CausedBy")]
[XmlArrayItem("SubClass1", typeof(Subclass1))]
[XmlArrayItem("SubClass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
public class Subclass1 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToShortDateString();
public String SubClass1Item { get { return "Test1 " + t; } set { } }
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToString();
public String SubClass2Item { get { return "Test2 " + t; } set { } }
}
It produces:
<Entity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CausedBy>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
</CausedBy>
</Entity>

Why is my game serializing this class?

So I'm making a game, and it saves users' progress on the computer in a binary file. The User class stores a few things:
Integers for stat values (Serializable)
Strings for the Username and the skin assets
Lists of both the Achievement class and the InventoryItem class, which I have created myself.
Here are the User fields:
public string Username = "";
// ID is used for local identification, as usernames can be changed.
public int ID;
public int Coins = 0;
public List<Achievement> AchievementsCompleted = new List<Achievement>();
public List<InventoryItem> Inventory = new List<InventoryItem>();
public List<string> Skins = new List<string>();
public string CurrentSkinAsset { get; set; }
The Achievement class stores ints, bools, and strings, which are all serializable. The InventoryItem class stores its name (a string) and an InventoryAction, which is a delegate that is called when the item is used.
These are the Achievement class's fields:
public int ID = 0;
public string Name = "";
public bool Earned = false;
public string Description = "";
public string Image;
public AchievmentDifficulty Difficulty;
public int CoinsOnCompletion = 0;
public AchievementMethod OnCompletion;
public AchievementCriteria CompletionCriteria;
public bool Completed = false;
And here are the fields for the InventoryItem class:
InventoryAction actionWhenUsed;
public string Name;
public string AssetName;
The source of the InventoryAction variables are in my XNAGame class. What I mean by this is that the XNAGame class has a method called "UseSword()" or whatever, which it passes into the InventoryItem class. Previously, the methods were stored in the Game1 class, but the Game class, which Game1 inherits from, is not serializable, and there's no way for me to control that. This is why I have an XNAGame class.
I get an error when trying to serialize: "The 'SpriteFont' class is not marked as serializable", or something like that. Well, there is a SpriteFont object in my XNAGame class, and some quick tests showed that this is the source of the issue. Well, I have no control over whether or not the SpriteFont class is Serializable.
Why is the game doing this? Why must all the fields in the XNAGame class be serializable, when all I need is a few methods?
Keep in mind when answering that I'm 13, and may not understand all the terms you're using. If you need any code samples, I'll be glad to provide them for you. Thanks in advance!
EDIT: One solution I have thought of is to store the InventoryAction delegates in a Dictionary, except that this will be a pain and isn't very good programming practice. If this is the only way, I'll accept it, though (Honestly at this point I think this is the best solution).
EDIT 2: Here's the code for the User.Serialize method (I know what I'm doing in inefficient, and I should use a database, blah, blah, blah. I'm fine with what I'm doing now, so bear with me.):
FileStream fileStream = null;
List<User> users;
BinaryFormatter binaryFormatter = new BinaryFormatter();
try
{
if (File.Exists(FILE_PATH) && !IsFileLocked(FILE_PATH))
{
fileStream = File.Open(FILE_PATH, FileMode.Open);
users = (List<User>)binaryFormatter.Deserialize(fileStream);
}
else
{
fileStream = File.Create(FILE_PATH);
users = new List<User>();
}
for (int i = 0; i < users.Count; i++)
{
if (users[i].ID == this.ID)
{
users.Remove(users[i]);
}
}
foreach (Achievement a in AchievementsCompleted)
{
if (a.CompletionCriteria != null)
{
a.CompletionCriteria = null;
}
if (a.OnCompletion != null)
{
a.OnCompletion = null;
}
}
users.Add(this);
fileStream.Position = 0;
binaryFormatter.Serialize(fileStream, users);
You cannot serialize a SpriteFont by design, actually this is possible (.XNB file) but it hasn't been made public.
Solution:
Strip it off your serialized class.
Alternatives:
If for some reasons you must serialize some font, the first thing that comes to my mind would be to roll-out your own font system such as BMFont but that's a daunting task since you'll have to use it everywhere else where you might already do ...
Generate a pre-defined amount of fonts (i.e. Arial/Times/Courier at size 10/11/12 etc ...) using XNA Content app (can't recall its exact name); then store this user preference as two strings. With a string.Format(...) you should be able to load the right font back quite easily.
Alternative 2 is certainly the easiest and won't take more than a few minutes to roll-out.
EDIT
Basically, instead of saving a delegate I do the following:
inventory items have their own type
each type name is de/serialized accordingly
their logic does not happen in the main game class anymore
you don't have to manually match item type / action method
So while you'll end up with more classes, you have concerns separated and you can keep your main loop clean and relatively generic.
Code:
public static class Demo
{
public static void DemoCode()
{
// create new profile
var profile = new UserProfile
{
Name = "Bill",
Gold = 1000000,
Achievements = new List<Achievement>(new[]
{
Achievement.Warrior
}),
Inventory = new Inventory(new[]
{
new FireSpell()
})
};
// save it
using (var stream = File.Create("profile.bin"))
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, profile);
}
// load it
using (var stream = File.OpenRead("profile.bin"))
{
var formatter = new BinaryFormatter();
var deserialize = formatter.Deserialize(stream);
var userProfile = (UserProfile) deserialize;
// set everything on fire :)
var fireSpell = userProfile.Inventory.Items.OfType<FireSpell>().FirstOrDefault();
if (fireSpell != null) fireSpell.Execute("whatever");
}
}
}
[Serializable]
public sealed class UserProfile
{
public string Name { get; set; }
public int Gold { get; set; }
public List<Achievement> Achievements { get; set; }
public Inventory Inventory { get; set; }
}
public enum Achievement
{
Warrior
}
[Serializable]
public sealed class Inventory : ISerializable
{
public Inventory() // for serialization
{
}
public Inventory(SerializationInfo info, StreamingContext context) // for serialization
{
var value = (string) info.GetValue("Items", typeof(string));
var strings = value.Split(';');
var items = strings.Select(s =>
{
var type = Type.GetType(s);
if (type == null) throw new ArgumentNullException(nameof(type));
var instance = Activator.CreateInstance(type);
var item = instance as InventoryItem;
return item;
}).ToArray();
Items = new List<InventoryItem>(items);
}
public Inventory(IEnumerable<InventoryItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
Items = new List<InventoryItem>(items);
}
public List<InventoryItem> Items { get; }
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
var strings = Items.Select(s => s.GetType().AssemblyQualifiedName).ToArray();
var value = string.Join(";", strings);
info.AddValue("Items", value);
}
#endregion
}
public abstract class InventoryItem
{
public abstract void Execute(params object[] objects);
}
public abstract class Spell : InventoryItem
{
}
public sealed class FireSpell : Spell
{
public override void Execute(params object[] objects)
{
// using 'params object[]' a simple and generic way to pass things if any, i.e.
// var world = objects[0];
// var strength = objects[1];
// now do something with these !
}
}
Okay, so I figured it out.
The best solution was to use a Dictionary in the XNAGame class, which stores two things: an ItemType (an enumeration), and an InventoryAction. Basically, when I use an item, I check it's type and then look up it's method. Thanks to everyone who tried, and I'm sorry if the question was confusing.

Collections Editor is not persisting entries in custom web control markup in design view in VS 2013?

I am trying to develop a simple custom web control for ASP.Net WebForms that has a collections property called Subscriptions.
I can compile the control project successfully and add it from toolbox to an aspx page without any issues.
The problem is when I add entries for Subscriptions property using the collections editor in design view in Visual Studio 2013.
I can input multiple Subscriptions but when I click on OK button of the collections editor and then I go back to Subscriptions property in design view it's empty even though I had input some entries a moment ago.
Markup of custom control in aspx
<cc1:WebControl1 ID="WebControl1" runat="server"></cc1:WebControl1>
Question : What is not correct with my code that is causing the collections to not show up in control's markup in design view?
Custom web control code
namespace WebControl1
{
[ToolboxData("<{0}:WebControl1 runat=\"server\"> </{0}:WebControl1>")]
[ParseChildren(true)]
[PersistChildren(false)]
public class WebControl1 : WebControl
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text
{
get
{
String s = (String)ViewState["Text"];
return ((s == null) ? "[" + this.ID + "]" : s);
}
set
{
ViewState["Text"] = value;
}
}
[
Category("Behavior"),
Description("The subscriptions collection"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
Editor(typeof(SubscriptionCollectionEditor), typeof(UITypeEditor)),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public List<Subscription> Subscriptions { get; set; }
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(Text);
}
}
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Subscription
{
private string name;
private decimal amount;
public Subscription()
: this(String.Empty, 0.00m)
{
}
public Subscription(string nm, decimal amt)
{
name = nm;
amount = amt;
}
[
Category("Behavior"),
DefaultValue(""),
Description("Name of subscription"),
NotifyParentProperty(true),
]
public String Name
{
get
{
return name;
}
set
{
name = value;
}
}
[
Category("Behavior"),
DefaultValue("0.00"),
Description("Amount for subscription"),
NotifyParentProperty(true)
]
public decimal Amount
{
get
{
return amount;
}
set
{
amount = value;
}
}
}
public class SubscriptionCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
public SubscriptionCollectionEditor(Type type)
: base(type)
{
}
protected override bool CanSelectMultipleInstances()
{
return false;
}
protected override Type CreateCollectionItemType()
{
return typeof(Subscription);
}
}
I was able to solve the problem by making following 2 changes.
Since for collections like List the .Net framework will automatically display an appropriate editor so we don't need to specify the editor since the collection is of List type. So we don't need this attribute Editor(typeof(SubscriptionCollectionEditor), typeof(UITypeEditor)).
The setter for Subscriptions needs to be removed and only a get should be there as in code below. If a setter is used then it should be used as in second code snippet. But automatic get and set should not be used with collection property in a custom web control.
The final code for the collections property should look like below and then collections will not disappear when one returns to it later on in design-time VS 2013.
Code that works without a setter
private List<Subscription> list = null;
[Category("Behavior"),
Description("The subscriptions collection"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public List<Subscription> SubscriptionList
{
get
{
if (lists == null)
{
lists = new List<Subscription>();
}
return lists;
}
}
Code that works with a setter
[Category("Behavior"),
Description("The subscriptions collection"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public List<Subscription> SubscriptionList
{
get
{
object s = ViewState["SubscriptionList"];
if ( s == null)
{
ViewState["SubscriptionList"] = new List<Subscription>();
}
return (List<Subscription>) ViewState["SubscriptionList"];
}
set
{
ViewState["SubscriptionList"] = value;
}
}

Cast IEnumerable to custom ObservableCollection Class?

Is it possible to convert IEnumerable to a Custom Class that is inherting from ObservableCollection class?
Reason is I want to select only a filtered set of items on the get. I want to implement it on the get because lots of other properties reference CustomItems and perform processes on the items, but I want to somehow make it process filtered set of items depending if a value is enabled or not.
Below is code to help explain what I want to achieve:
public class CustomItemsCollection : ObservableCollection<ItemViewModel>
{
public ListView ListView { get; set; }
public void ScrollToItem(object item = null)
{
//Some custom Code
}
}
And here is my property that I want to customize:
private CustomItemsCollection _CustomItems = null;
[JsonProperty]
public CustomItemsCollection CustomItems
{
get
{
if (_CustomItems != null)
{
if(SomeValueIsEnabled)
{
var filteredItems = _CustomItems.Where(c => c.Property.equals(SomeValue));
var castedItems = (CustomItemsCollection)filteredItems;
return castedItems;
}
return _CustomItems;
}
_CustomItems = new CustomItemsCollection();
_CustomItemsChangedSource = new CollectionChangedWeakEventSource();
_CustomItemsChangedSource.SetEventSource(_CustomItems);
_CustomItemsChangedSource.CollectionChanged += _CustomItemsChangedSource_CollectionChanged;
return _CustomItems;
}
set { _CustomItems = value; RaisePropertyChanged("CustomItems"); }
}
Specifically, this part:
if(SomeValueIsEnabled)
{
var filteredItems = _CustomItems.Where(c => c.Property.equals(SomeValue));
var castedItems = (CustomItemsCollection)filteredItems;
return castedItems;
}
Is this possible / or maybe wrong? What is the best practice to do it?
Thank you!
You can't just cast it, but you can create an instance of CustomItemsCollection and initialize it with filteredItems.
Add a constructor to your custom class that passes through to the appropriate ObservableCollection constructor:
public class CustomItemsCollection : ObservableCollection<ItemViewModel>
{
public CustomItemsCollection(IEnumerable<ItemViewModel> items)
: base(items) { }
// your other code here
}
Then you can do this:
var filteredItems = _CustomItems.Where(c => c.Property.equals(SomeValue));
var collection = new CustomItemsCollection(filteredItems);
return collection;
Try with this code:
var filteredItems = _CustomItems.Where(c => c.Property.equals(SomeValue))
.Select(pre=> new ItemViewModel(){
//add info here
});
var castedItems = new CustomItemsCollection(filteredItems);

Show ComboBox group header for Silverlight

I want to show a ComboBox with OPTGROUP style header gruopings in Silverlight. Every website I find (including questions on SO) that sovle this link to an outdated link and, handily, show no code snippets for me to work from.
E.g.:
So how do I do this?
See my similar question: How to show group header for items in Silverlight combobox?
I put dummy entries in collection source to indicate the group header and then modified DataTemplate to show the group headers in different way and normal entries in separate way.
Here is one generalized approach. It's not bad, and should be flexible enough -- although it has the weakness of requiring an extended collection class (cannot make use of CollectionViewSource).
Step 1 ComboBoxGroupHeader object
This plays the role of "dummy entry" mentioned by #NiteshChordiya.
public class ComboBoxGroupHeader
{
public ComboBoxGroupHeader(object header)
{
Header = header;
}
public object Header { get; protected set; }
}
Step 2 Extended ComboBox
This overrides PrepareContainerForItemOverride, in order to tinker with the dummy items' containers. It also provides an (optional) "HeaderTemplate".
public class GroupedComboBox : ComboBox
{
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
}
public static readonly DependencyProperty HeaderTemplateProperty =
DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(GroupedComboBox), new PropertyMetadata(null));
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var container = element as ComboBoxItem;
if (container != null && item is ComboBoxGroupHeader)
{
// prevent selection
container.IsHitTestVisible = false;
// adjust the container to display the header content
container.ContentTemplate = HeaderTemplate
container.Content = ((ComboBoxGroupHeader)item).Header;
}
}
}
Step 3 Extended collection class
This does the grouping, and adds the dummy "ComboBoxGroupHeader" entries. This implementation is essentially read-only (groupings would break if you tried to add new items), but it would be straight-forward to support operations like "Add", "Insert", etc.
public class GroupedCollection<T, TGroup> : ObservableCollection<object>
{
private Func<T, TGroup> _grouping;
public IEnumerable<T> BaseItems
{
get { return base.Items.OfType<T>(); }
}
public GroupedCollection(IEnumerable<T> initial, Func<T, TGroup> grouping)
: base(GetGroupedItems(initial, grouping))
{
_grouping = grouping;
}
private static IEnumerable<object> GetGroupedItems(IEnumerable<T> items, Func<T, TGroup> grouping)
{
return items
.GroupBy(grouping)
.SelectMany(grp =>
new object[] { new ComboBoxGroupHeader(grp.Key) }
.Union(grp.OfType<object>())
);
}
}
Usage
It works like a normal ComboBox, with the optional HeaderTemplate:
<local:GroupedComboBox ItemsSource="{Binding Source}">
<local:GroupedComboBox.HeaderTemplate>
<TextBlock FontSize="9" TextDecorations="Underline" Foreground="DarkGray"
Text="{Binding}" />
</local:GroupedComboBox.HeaderTemplate>
</local:GroupedComboBox>
For the grouped to take effect, the source must use the "GroupedCollection" above, so as to include the dummy header items. Example:
public class Item
{
public string Name { get; set; }
public string Category { get; set; }
public Item(string name, string category)
{
Name = name;
Category = category;
}
}
public class Items : GroupedCollection<Item, string>
{
public Items(IEnumerable<Item> items)
: base(items, item => item.Category) { }
}
public class ViewModel
{
public IEnumerable<Item> Source { get; private set; }
public ViewModel()
{
Source = new Items(new Item[] {
new Item("Apples", "Fruits"),
new Item("Carrots", "Vegetables"),
new Item("Bananas", "Fruits"),
new Item("Lettuce", "Vegetables"),
new Item("Oranges", "Fruits")
});
}
}
Group header is not supported in ComboBox. Alternate way for to implement it with DataGrid or use TreeView to show grouped data.
However, you can try something like this,
Silverlight Custom ComboBox

Categories

Resources