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);
}
}
Related
I created a ListBoxItem where I have a property Name and override ToString() to give back name. That works nicely when I add new items.
But now I need to force the ListBox to update the labels when I change the name of my ship. I thought Refresh or Update would do that but that doesn't work.
I might be missing something very easy here.
public class ShipListBoxItem
{
public ListBox Parent { get; set; }
public ShipType Ship { get; set; }
public ShipListBoxItem()
{
Ship = new ShipType();
}
public ShipListBoxItem(ShipType st)
{
Ship = st;
}
public override string ToString()
{
return Ship.Name;
}
public void UpdateListBox()
{
Parent.Refresh(); //My problem is here. Update doesn't work either.
}
public static ShipListBoxItem AddToListBox(ListBox lb, ShipType ship)
{
ShipListBoxItem li = new ShipListBoxItem(ship);
li.Parent = lb;
lb.Items.Add(li);
return li;
}
}
If you use a List<T> as the DataSource for the listbox it is pretty easy to have changes to items show up. It also means there is no real reason to have a special class for adding a ShipListBoxItem to a ListBox, your basic Ship class may work:
class ShipItem
{
public enum ShipTypes { BattleShip, Carrier, Destroyer, Submarine, Frigate };
public ShipTypes Ship { get; set; }
public string Name { get; set; }
public ShipItem(string n, ShipTypes st)
{
Name = n;
Ship = st;
}
public override string ToString()
{
return String.Format("{0}: {1}", Ship.ToString(), Name);
}
}
The form related stuff:
private void Form1_Load(object sender, EventArgs e)
{
// add some ships
Ships = new List<ShipItem>();
Ships.Add(new ShipItem("USS Missouri", ShipTypes.BattleShip));
Ships.Add(new ShipItem("USS Ronald Reagan", ShipTypes.Carrier));
lb.DataSource = Ships;
}
private void button1_Click(object sender, EventArgs e)
{
// change a ship name
lb.DataSource = null; // suspend binding
this.Ships[0].Name = "USS Iowa";
lb.DataSource = Ships; // rebind
lb.Refresh();
}
As an alternative, you can also tell the Listbox to use a specific property for the display using DisplayMember:
lb.DataSource = Ships;
lb.DisplayMember = "Name";
This would use the Name property in the listbox instead of the ToString method. If your list is changing a lot, use a BindingList instead. It will allow changes to the list show up in the ListBox as you add them without toggling the DataSource.
Try this
ListBox.RefreshItems()
msdn
EDIT: You can use an extended class like this:
public class FooLisBox : System.Windows.Forms.ListBox
{
public void RefreshAllItems()
{
RefreshItems();
}
}
private void button1_Click(object sender, EventArgs e)
{
(listBox1.Items[0] as ShipListBoxItem).Ship.Name = "AAAA";
listBox1.RefreshAllItems();
}
I managed to solve my problem.
Mostly, thanks Jose M.
I ran into a problem however. RefreshItems() triggers OnSelectedIndexChanged()
so my overridden class looks like this
public class MyListBox : ListBox
{
public bool DoEvents{ get; set; } // Made it public so in the future I can block event triggering externally
public MyListBox()
{
DoEvents = true;
}
public void RefreshAllItems()
{
SuspendLayout();
DoEvents = false;
base.RefreshItems(); // this triggers OnSelectedIndexChanged as it selects the selected item again
DoEvents = true;
ResumeLayout();
}
// I only use this event but you can add all events you need to block
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (DoEvents)
base.OnSelectedIndexChanged(e);
}
}
I have a series of nested collections that I need to detect a change anywhere from the uppermost to lowermost layer. First, I'll show my code:
public class CategoricalDataItem
{
public string XValue { get; set; }
public double YValue { get; set; }
public SolidColorBrush Color { get; set; }
public CategoricalDataItemCollection SimpleSubData { get; set; }
}
[Serializable]
public class CategoricalDataItemCollection : ObservableCollection<CategoricalDataItem>
{
public string Descriptor { get; set; }
}
This code is structured for drill-down charting. Basically, I am allowing 5 layers deep. A developer could create an instance of CategoricalDataItemCollection and for each CategoricalDataItem within that collection, a new instance of CategoricalDataItemCollection could be created and so on. I am needed to be made aware if any item is added or removed from any of these nested collections. CollectionChanged event only detects a change in the first layer. Suggestions would be appreciated.
The trick is monitoring the collection for changes. Below is an example for doing so.
I included a small test class that will spit out messages as new items are added.
The key thing to note is that you are essentially consuming your own inherited CollectionChanged event so that you can monitor the children coming in and out and listening for when their collection changes. Please pay special attention that this is purely an example and needs some polishing and testing when it comes to the MyCollectionChanged(...) as there are other NotifyCollectionChangedActions that need to be handled.
[Serializable]
public class CategoricalDataItemCollection : ObservableCollection<CategoricalDataItem>
{
public string Descriptor { get; set; }
public CategoricalDataItemCollection()
{
this.CollectionChanged += MyCollectionChanged;
}
void MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// There are other actions to handle. This is purely an example.
if (args.OldItems != null)
{
foreach (var oldItem in args.OldItems.Cast<CategoricalDataItem>())
{
if (args.Action == NotifyCollectionChangedAction.Remove)
oldItem.SimpleSubData.CollectionChanged -= InvokeCollectionChanged;
}
}
if (args.NewItems != null)
{
foreach (var newItem in args.NewItems.Cast<CategoricalDataItem>())
{
if (args.Action == NotifyCollectionChangedAction.Add)
newItem.SimpleSubData.CollectionChanged += InvokeCollectionChanged;
}
}
}
void InvokeCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// This is the tricky part. Nothing actually changed in our collection, but we
// have to signify that something did.
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public class TestClass
{
public void TestNotify()
{
var parent = new CategoricalDataItemCollection();
parent.CollectionChanged += (sender, args) => Debug.Print("Parent Collection Changed");
var child = new CategoricalDataItem {SimpleSubData = new CategoricalDataItemCollection()};
child.SimpleSubData.CollectionChanged += (sender, args) => Debug.Print("Child Collection Changed");
var grandChild = new CategoricalDataItem { SimpleSubData = new CategoricalDataItemCollection()};
grandChild.SimpleSubData.CollectionChanged += (sender, args) => Debug.Print("Grand Child Collection Changed");
//Should only output "Parent"
parent.Add(child);
//Should only output "Child" and then "Parent"
child.SimpleSubData.Add(grandChild);
//Should now output "Grand Child" and then "Child" and then "Parent" messages.
grandChild.SimpleSubData.Add(new CategoricalDataItem(){SimpleSubData = new CategoricalDataItemCollection()});
}
}
This is my first C# application, entirely self-taught without any prior software programming background. I did some research on Undo/Redo but could not find anything helpful (or easy to understand). Therefore, I'm hoping someone can help me in designing undo/redo function for my program (winforms application). The application consist of a main form where subsequent child forms will be called to record user specified values during certain events (button clicks etc). After every event is handled, a bitmap will be drawn in buffer and then loaded to a picturebox within the main form during the OnPaint event of the main form. Each input in separated into custom class objects and added into separate List and BindingList. Objects contained within List are used for graphics (to indicate coordinates etc) while objects in BindingList are used to display some important values on DataGridView. Just to give a brief description, the codes look something like this:
public partial class MainForm : form
{
public class DataClass_1
{
public double a { get; set; }
public double b { get; set; }
public SubDataClass_1 { get; set; }
}
public class SubDataClass_1
{
public double x { get; set; }
public double y { get; set; }
public string SomeString { get; set; }
public CustomEnum Enum_SubDataClass_1 { get; set; }
}
public class DisplayDataClass
{
public string SomeString { get; set; }
public double e { get; set; }
public double f { get; set; }
}
public enum CustomEnum { Enum1, Enum2, Enum3 };
// Lists that contain objects which hold the necessary values to be drawn and displayed
public List<DataClass_1> List_1 = new List<DataClass_1>();
public List<DataClass_2> List_2 = new List<DataClass_2>(); // Object has similar data types as DataClass_1
public BindingList<DisplayDataClass> DisplayList = new BindingList<DisplayDataClass>();
Bitmap buffer;
public MainForm()
{
InitializeComponent();
dgv.DataSource = DisplayList;
}
private void DrawObject_1()
{
// some drawing codes here
}
private void DrawObject_2()
{
// some drawing codes here
}
protected override void OnPaint(PaintEventArgs e)
{
DrawObject_1();
DrawObject_2();
pictureBox1.Image = buffer;
}
// Event to get input
private void action_button_click(object sender, EventArgs e)
{
ChildForm form = new ChildForm(this);
form.ShowDialog();
Invalidate();
}
}
The child forms' codes look something like this:
public partial class ChildForm : form
{
public ChildForm(MainForm MainForm)
{
InitializeComponent();
// Do something
}
private void ok_button_click(object sender, EventArgs e)
{
DataClass_1 Data_1 = new DataClass_1();
DisplayDataClass DisplayData = new DisplayDataClass();
// Parsing, calculations, set values to Data_1 and DisplayData
MainForm.List_1.Add(Data_1);
MainForm.DisplayList.Add(DisplayData);
this.DialogResult = System.Windows.Forms.DialogResult.OK;
this.Close();
}
}
Since all necessary data are stored in the lists and will only be changed after certain events are triggered (mostly button clicks), therefore I tried to use these lists to determine the state of the application during run time. My approach in implementing the undo/redo function is by adding the following codes:
public partial class MainForm : form
{
public class State()
{
public List<DataClass_1> List_1 { get; set; }
public List<DataClass_2> List_2 { get; set; }
public BindingList<DisplayDataClass> DisplayList { get; set; }
// and so on
public State()
{
List_1 = new List<DataClass_1>();
List_2 = new List<DataClass_2>();
DisplayList = new BindingList<DisplayDataClass>();
}
}
State currentState = new State();
Stack<State> undoStack = new Stack<State>();
Stack<State> redoStack = new Stack<State>();
private void MainForm_Shown(object sender, EventArgs e)
{
// Saves original state as first item in undoStack
undoStack.Push(currentState);
}
protected override void OnPaint(PaintEventArgs e)
{
// Update lists from currentState before drawing
List_1 = new List<DataClass_1>(currentState.List_1);
List_2 = new List<DataClass_2>(currentState.List_2);
DisplayList = new BindingList<DisplayDataClass>(currentState.DisplayList);
}
// When undo button is clicked
private void undo_button_Click(object sender, EventArgs e)
{
if (undoStack.Count > 0)
{
redoStack.Push(currentState);
undoStack.Pop();
currentState = undoStack.Last();
Invalidate();
}
}
// When redo button is clicked
private void redo_button_Click(object sender, EventArgs e)
{
// Have not thought about this yet, trying to get undo done first
}
// Events that trigger changes to values held by data objects
private void action_button_Click(object sender, EventArgs e)
{
// Replace the following code with previously stated version
if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
ChildForm form = new ChildForm(this)
UpdateState();
undoStack.Push(currentState);
Invalidate();
}
}
// To update currentState to current values
private void UpdateState()
{
currentState.List_1 = new List<DataClass_1>(List_1);
currentState.List_2 = new List<DataClass_2>(List_2);
currentState.DisplayList = new BindingList<DisplayDataClass>(DisplayList);
// and so on
}
}
Result:
The application does not perform the undo function correctly. The program shows the correct output under normal conditions but when the undo event is triggered, regardless of how many objects have been drawn, the application reverts back to initial state (the state where there is no recorded data). I've used System.Diagnostics.Debug.WriteLine() during events where the stack is changed to check the number of counts within undoStack and it seems to give the correct counts. I'm guessing that the lists need to be copied/cloned in a different manner? Or am I doing something wrong here? Can anyone please guide me? Performance, readability, resource management, future maintenance and etc need not be considered.
There are a lot of approaches that will work, each with different strengths and weaknesses, but I generally like to define an abstract Action class and then a separate UndoRedoStack class.
The Action class would have two methods (Do and Undo) which each subclassed Action can implement. You isolate any logic that can "change state" to these Action subclasses thereby keeping that logic neatly encapsulated.
The UndoRedoStack is like a regular stack except with three core methods.
ApplyAction (like Push)
UndoAction (like Pop, but be sure to only
move the pointer/index without truncating or throwing away any
existing actions).
RedoAction (like Push, but you use the next value
already in the underlying stack/list instead of pushping/inserting a
new one).
Usually I find the biggest challenge then becomes designing each Action subclass in such a way that it maintains enough information to both undo and redo itself. But being able to encapsulate all state manipulation logic to individual Action subclasses usually makes it easiest for me to maintain in the long run.
You are storing reference objects in your stacks. If you want your method to work, you need to implement a clone() method in your state object, and store a new clone each time, otherwise, changes made are made to each member of the stack, as they all point to the same reference object.
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.
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