Trying to implement Serializble DIctionary from Keyedcollection, unable to add objects - c#

Long story short, I needed a set of objects with dictionary-like functionality that can be serialized in order to save user data. The original dictionary was a Dictionary class that held an array of Item objects and the amounts of each object 'held' by the user. After finding some recommendations on the internet I tried implmenting my own dictionary-like class from KeyedCollection, but can't seem to add objects to it. Am I adding the objects wrong or is something wrong with my collection?
The 'SerialDictionary' class:
public class SerialDictionary : KeyedCollection<Item, int>
{
protected override int GetKeyForItem(Item target)
{
return target.Key;
}
}
public class Item
{
private int index;
private string attribute;
public Item(int i, string a)
{
index = i;
attribute = a;
}
public int Key
{
get { return index; }
set { index = value; }
}
public string Attribute
{
get { return attribute; }
set { attribute = value; }
}
}
The Main form (that is trying to add the object)
public partial class Form1 : Form
{
SerialDictionary ItemList;
Item orb;
public Form1()
{
InitializeComponent();
ItemList = new SerialDictionary();
orb = new Item(0001, "It wants your lunch!");
orb.Key = 001;
}
private void button1_Click(object sender, EventArgs e)
{
ItemList.Add(orb);
}
}
The error I am receiving when trying to add an object:
The best overloaded method match for 'System.Collections.ObjectModel.Collection.Add(int)' has some invalid arguments
If I throw an int in there it compiles, but I'm trying to get a collection of the Item objects in there...

You have it backwards, it should be:
public class SerialDictionary : KeyedCollection<int, Item>
The key type comes first in the signature, then the item type.

Related

How to change ItemsSource if the ObservableCollection in another class?

I want to change my ItemsSource but i set the ObservableCollection in another class. How can i Add something to my ItemsSource if the ObservableCollection isn't there?
in Edit window :
private void manageLayout_Click(object sender, Telerik.Windows.RadRoutedEventArgs e)
{
...
scheduleDetail = assetListClass.GetScheduleDetail(xmlScheduleDetail);
ObservableCollection<LibraryData> scheduleDetailOC = new ObservableCollection<LibraryData>(scheduleDetail);
ManageLayout manageLayoutWin = new ManageLayout();
this.Close();
manageLayoutWin.Show();
manageLayoutWin.ManageLayout_GridView.ItemsSource = scheduleDetailOC;
...
}
if it's in the same class i can just use this code :
scheduleDetailOC.Add(abc);
but what if it's in another class? What should i do in ManageLayout window to change the ItemsSource? i tried this :
ManageLayout_GridView.Items.Add(abc);
and i've got an error :
Operation is not valid while ItemsSource is in use. Access and modify
elements with ItemsControl.ItemsSource instead
First of all you need to store the reference to your collection in your class field/property. To have an access from another classes this property should be public.
public class FirstClass
{
public ObservableCollection<LibraryData> ScheduleDetails { get; private set; }
private void manageLayout_Click(object sender, Telerik.Windows.RadRoutedEventArgs e)
{
...
scheduleDetail = assetListClass.GetScheduleDetail(xmlScheduleDetail);
ScheduleDetails = new ObservableCollection<LibraryData>(scheduleDetail);
ManageLayout manageLayoutWin = new ManageLayout();
this.Close();
manageLayoutWin.Show();
manageLayoutWin.ManageLayout_GridView.ItemsSource = ScheduleDetails;
...
}
}
Now you can manipulate collection by reference to the first class. You can use Dependency injection to save a refernce. If your second class needs to add elements to the first class the simplest way is to take a constructor argument:
public class AnotherClass
{
private readonly FirstClass collectionHolder;
public AnotherClass(FirstClass collectionHolder)
{
this.collectionHolder = collectionHolder;
}
public void AddElement()
{
var newElement = GetNewElement(); // creates element that will be add to the collection
collectionHolder.ScheduleDetails.Add(newElement);
}
}
It will works but not good because now AnotherClass knows all about FirstClass public interface. The other reason is that all classes that have reference to the FirstClass can manipulate public collection.
The good design is to create new interface for your FirstClass that will contains only allowed operations and use it in AnotherClass.
public interface IScheduleDetailsCollectionHolder
{
void AddElement(LibraryData data);
}
public class FirstClass : IScheduleDetailsCollectionHolder
{
private ObservableCollection<LibraryData> scheduleDetails;
private void manageLayout_Click(object sender, Telerik.Windows.RadRoutedEventArgs e)
{
...
scheduleDetail = assetListClass.GetScheduleDetail(xmlScheduleDetail);
scheduleDetails = new ObservableCollection<LibraryData>(scheduleDetail);
ManageLayout manageLayoutWin = new ManageLayout();
this.Close();
manageLayoutWin.Show();
manageLayoutWin.ManageLayout_GridView.ItemsSource = scheduleDetails;
...
}
public void AddElement(LibraryData data)
{
scheduleDetails.Add(data);
}
}
public class AnotherClass
{
private readonly IScheduleDetailsCollectionHolder collectionHolder;
public AnotherClass(IScheduleDetailsCollectionHolder collectionHolder)
{
this.collectionHolder = collectionHolder;
}
public void AddElement()
{
var newElement = GetNewElement(); // creates element that will be add to the collection
collectionHolder.AddElement(newElement);
}
}
The other advice is to use MVVM pattern and data binding that is standard de facto for WPF applications.

Get/Set for a Private Static Collection?

I've defined a class with the following property:
private ObservableCollection<Job> allJobs;
Access is defined as follows:
public ObservableCollection<Job> AllJobs
{
get
{
return this.allJobs;
}
set
{
this.allJobs = value;
}
}
The get set works fine when I assign a whole ObservableCollection to the property, the set works fine for retrieving it obviously. But why have I lost all the methods that normally allow me to 'Add' (i.e. add 1 job to the collection)?
At the moment I'm having to create a temporary collection to populate to then assign to the allJobs property and I shouldn't have to.
Any ideas??
What do you mean with 'lost methods'? Have you tried AllJobs.Add()? The following code works for me:
void Main()
{
AllJobs = new ObservableCollection<Job>();
AllJobs.Add(new Job());
}
public class Job { }
private ObservableCollection<Job> allJobs;
public ObservableCollection<Job> AllJobs
{
get
{
return this.allJobs;
}
set
{
this.allJobs = value;
}
}
EDIT:
Based on your comment I've amended my code as follows but everything still works for me, I have noticed however that you don't seen to initialise the allJobs collection anywhere.
void Main()
{
PresentationManager.Instance.AllJobs.Add(new Job());
}
public class Job { }
sealed class PresentationManager
{
public static readonly PresentationManager Instance = new PresentationManager();
private PresentationManager()
{
allJobs = new ObservableCollection<Job>();
}
private ObservableCollection<Job> allJobs;
public ObservableCollection<Job> AllJobs
{
get { return this.allJobs; }
set { this.allJobs = value; }
}
}
Normally you wouldn't want a setter for such a property, as you would lose all events bound to the ObservableCollection when the setter is used.
public ObservableCollection<Job> AllJobs { get; private set; }

Why isn't this ref parameter changing the value passed in?

The variable asynchExecutions does get changed, but it doesn't change the reference variable.Simple question, why isn't this ref parameter in this constructor changing the original value passed in?
public partial class ThreadForm : Form
{
int asynchExecutions1 = 1;
public ThreadForm(out int asynchExecutions)
{
asynchExecutions = this.asynchExecutions1;
InitializeComponent();
}
private void start_Button_Click(object sender, EventArgs e)
{
int.TryParse(asynchExecution_txtbx.Text, out asynchExecutions1);
this.Dispose();
}
}
How do you know that asynchExecutions is not changing? Can you show your testcase code that proves this?
It appears that on constructing ThreadForm asynchExecutions will be set to 1. However when you call start_Button_Click, you set asyncExecutions1 to the value in the text box.
This WILL NOT set asyncExecutions to the value in the text box, because these are value types. You are not setting a pointer in the constructor.
It seems to me that you are confused between the behavior of value types versus reference types.
If you need to share state between two components, consider using a static state container, or passing in a shared state container to the constructor of ThreadForm. For example:
public class StateContainer
{
public int AsyncExecutions { get; set;}
}
public class ThreadForm : Form
{
private StateContainer _state;
public ThreadForm (StateContainer state)
{
_state = state;
_state.AsyncExecutions = 1;
}
private void start_Button_Click(object sender, EventArgs e)
{
Int.TryParse(TextBox.Text, out _state.AsyncExecutions);
}
}
The out parameter is only good for the method call, you can't "save" it to update later.
So in your start_Button_Click, you can't change the original parameter passed to your form constructor.
You could do something like:
public class MyType {
public int AsynchExecutions { get; set; }
}
public partial class ThreadForm : Form
{
private MyType type;
public ThreadForm(MyType t)
{
this.type = t;
this.type.AsynchExecutions = 1;
InitializeComponent();
}
private void start_Button_Click(object sender, EventArgs e)
{
int a;
if (int.TryParse(asynchExecution_txtbx.Text, out a))
this.type.AsynchExecutions = a;
this.Dispose();
}
}
That will update the AsynchExecutions property of the instance of MyType.

How to combine two observable collections into collection in Silverlight

I am currently trying to combine two collections into one for binding to a combobox. I first started out with two static collections built within a class:
public partial class MainPage : UserControl
{
//create static observable collection
private ObservableCollection<string> items;
public ObservableCollection<string> Items
{
get
{
return this.items;
}
set
{
if (this.items != value)
{
this.items = value;
}
}
}
protected ObservableCollection<string> StaticItems
{
get
{
return new ObservableCollection<string>() { "Select User", "Select All" };
}
}
//create dynamic observable collection
public MainPage()
{
InitializeComponent();
this.items = this.StaticItems;
this.comboBox1.ItemsSource = this.Items;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
foreach (var item in GetDynamicItems())
{
this.Items.Add(item);
}
}
private List<string> GetDynamicItems()
{
return new List<string>() { "User1", "User2", "User3" };
}
The above works as desired.
What I would like to do now is to initate a query to a service and have the results of that service appended to the collection instead of User1, USer2,USer3
I create a query to the service as:
private void FillOfficerList()
{
QueryClient qc = new QueryClient("BasicHttpBinding_IQuery");
qc.GetOfficerNamesCompleted += new EventHandler<GetOfficerNamesCompletedEventArgs>(qc_GetOfficerNamesCompleted);
qc.GetOfficerNamesAsync();
}
public void qc_GetOfficerNamesCompleted(object sender, GetOfficerNamesCompletedEventArgs e)
{
// Now how do I add e.Results to above collection?
}
The query works I am just stuck on how to take the results ( e.Results) and bind/concat them to the Items collection. Any pointers or tips would be appreciated.
Note: This is for silverlight so using a composite collections approach does not seem to be an option as the class is not supported.
Thanks in advance
I just read your comment. Since you have the ObservableCollection with 3 strings and 1 int. Try doing this.
Lets assume you are having a Class say myClass which has the 3 strings and 1 int.
public class myClass()
{
string str1 {get; set;}
string str2 {get; set;}
string str3 {get; set;}
int int1 {get; set;}
}
Create an ObservableCollection in the client side with the same datatype.
ObservableCollection<myClass> collection = new ObservableCollection<myClass>();
public void qc_GetOfficerNamesCompleted(object sender, GetOfficerNamesCompletedEventArgs e)
{
// Now try adding this code
for(int i=0; i<e.Result.Count;i++)
{
// I do this because, I don't want the Client class to be unaware of the class myClass
collection.Add(new myClass()
{
str1 = e.Result[i].str1,
str2 = e.Result[i].str2,
str3 = e.Result[i].str3,
int1 = e.Result[i].int1
});
}
for(int i=0; i<collection.Count;i++)
{
Items.Add(collection[i].str1); // Add the string you want. I ve used str1 here.
}
}
Hope this helps.
Maybe I'm missing something, but as long as your service reference is using ObservableCollection as its collection type shouldn't you just be able to iterate over the results and Add() each item onto this.Items, just like you did with the dynamic items?
public void qc_GetOfficerNamesCompleted(object sender, GetOfficerNamesCompletedEventArgs e)
{
// Now how do I add e.Results to above collection?
foreach(var item in e.Results)
{
this.Items.Add(item);
}
}
I'm guessing I'm missing something. Can't you just do this?
public void qc_GetOfficerNamesCompleted(object sender, GetOfficerNamesCompletedEventArgs e)
{
foreach (var result in e.Results)
{
Items.Add(result);
}
}
If the service that is returning the result is of type ObservableCollection or if you are getting the result from the service as an Observable Collection(Say your service returns a List<> and if your collection type is ObservableCollection<>). You can append the items to the existing ObservableCollection. To confirm whether the return type of "e" is ObservableCollection:
Right Click the ServiceReference and Click Configure Service Reference. If the Collection type is List<>. You cannot add it to the ObservableCollection. So change it to ObservableCollection and if you wish the return type of the service also to ObservableCollection.
public void qc_GetOfficerNamesCompleted(object sender, GetOfficerNamesCompletedEventArgs e)
{
// Now try adding this code
for(int i=0; i<e.Result.Count;i++)
{
Items.Add(e.Result[i]); //Add individual item in the returning ObservableCollection to the items Collection
}
}
Hope this helps.
Thank you to everyone who helped out. Below is the final solution in working format. I had a couple of issues in the original code. Thanks to Aswin Ramakrishnan for indirectly pointing me to my collection types. I defaulted to usins obserColl when I should have referenced the original types from the WCF endpoint. This is where I was getting one error. The new code looks like this:
private ObservableCollection<MeDepartment> deptitems;
public ObservableCollection<MeDepartment> DeptItems
{
get
{
return this.deptitems;
}
set
{
if (this.deptitems != value)
{
this.deptitems = value;
}
}
}
protected ObservableCollection<MeDepartment> deptStaticItems
{
get
{
return new ObservableCollection<MeDepartment>()
{
new MeDepartment{Name = "Department"},
new MeDepartment{Name = "Select All"}
};
}
}
Next I needed to creat an onload event and query my WCF services for the department names
private void meFilter_Loaded(object sender, RoutedEventArgs e)
{
QueryClient qc = new QueryClient("BasicHttpBinding_IQuery");
qc.GetDepartmentsCompleted += new EventHandler<GetDepartmentsCompletedEventArgs>(qc_GetDepartmentsCompleted);
qc.GetDepartmentsAsync();
}
public void qc_GetDepartmentsCompleted(object sender, GetDepartmentsCompletedEventArgs e)
{
DeptItems = new ObservableCollection<MeDepartment>(deptStaticItems.Concat<MeDepartment>(e.Result));
DeptComboBox.ItemsSource = this.DeptItems;
DeptComboBox.SelectedIndex = 0;
}
Using the correct collection type (MeDepartment) allowed me to then properly concatonate the two collections together. (be sure to use the system.linq reference)
The final line was to repoint the combo box items source to the new collection.
Hope this helps others for future reference.
thanks again to all that contributed.
This is an old thread, but I just had this same issue and this is the solution I came up with.
interface IMyInterface{ string TheString{get;set}}
MyClass1 : IMyInterface {...}
MyClass2 : IMyInterface {...}
public ObservableCollection<IMyInterface> {get;set;}
You can then add both types to the collection without getting an error.
MyCollection.Add(new MyClass1());
MyCollection.Add(new MyClass2());
This is an example of combining two collection and sorting them:
this._sortedItems = new ObservableCollection<ILookupListItemEntity>(
LookupListItemEntities.Cast<ILookupListItemEntity>().Union(this.CustomLookupListItemEntities).OrderBy(a => a.Value).ToList());
Greg

Drag'n'drop data between instances

I'm attempting to allow my users to drag and drop certain rows of data from one custom list control to another, where the second list control is in another instance of the same application.
DoDragDrop(parameterTypedListView.SelectedObjects, DragDropEffects.Copy);
where parameterTypedListView.SelectedObjects is a generic IList where T is a custom class containing only valuetypes as fields/properties.
In the OnDragDrop event I try to extract this data but only get a System.__ComObject ... object which seems to inherit from System.MarshalByRefObject.
In short: How do I extract the data in an object oriented format I can actually use?
Edit: Setting my custom class as serializable has no discernible effect whatsoever. I can enumerate the __ComObject:
foreach (var dataObject in (IEnumerable) e.Data.GetData("System.Collections.ArrayList"))
{
// this actually enumerates the correct number of times, i.e. as many times as there are items in the list.
}
but every dataObject is, in itself, a System.__ComObject that I cannot cast to anything useful.
I was able to replicate your initial problem, but as soon as I added the [Serializable] attribute to the class in the array list, I was able to see the objects as their correct type.
Here is some example code, showing a small working example.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
this.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter);
}
[Serializable]
class DragClass
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
}
private void label1_MouseDown(object sender, MouseEventArgs e)
{
System.Collections.ArrayList aDragClasses = new System.Collections.ArrayList();
aDragClasses.Add(new DragClass() { Prop1 = "Test1", Prop2 = 2 });
aDragClasses.Add(new DragClass() { Prop1 = "Test2", Prop2 = 3 });
aDragClasses.Add(new DragClass() { Prop1 = "Test3", Prop2 = 4 });
DoDragDrop(aDragClasses, DragDropEffects.Copy);
}
private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
foreach (var aData in (System.Collections.IEnumerable)e.Data.GetData(typeof(System.Collections.ArrayList)))
{
System.Diagnostics.Debug.WriteLine(((DragClass)aData).Prop1);
}
}
}
I think that the problem is that you are using the list directly to pass the data. I tried it several different ways to get it to fail and figured out a few ways that it doesn't work.
If you don't have the [Serializable] attribute on your custom classes it will not work correctly because this is how the classes are marshaled between the processes. Also, if I use a List directly to pass the data I get a null reference exception.
If you use a simple transport class to pass the data (and all the types are serializable) then everything worked fine for me.
[Serializable]
class Test
{
public string Name { get; set; }
public string Description { get; set; }
}
[Serializable]
class Transport
{
public Transport()
{
this.Items = new List<Test>();
}
public IList<Test> Items { get; private set; }
}
Then I can do this no problem and it works across instances...
private void Form1_DragDrop(object sender, DragEventArgs e)
{
foreach (var item in ((Transport)e.Data.GetData(typeof(Transport))).Items)
{
System.Diagnostics.Debug.WriteLine(item.Name + " " + item.Description);
}
}

Categories

Resources