I've got a simple form which scans my network and finds computers. I dynamically add a user control for each computer found. Within each user control is a dropdown list that I need to manage. Here's a screenshot of the UI:
Each listbox may contain 1 of 2 possible lists. For simplicity sake, lets say the left column listboxes are bound to the laptops.xml datasource, while the right column listboxes are bound to the servers.xml.
Lets say the laptops.xml contains the following entries:
Dell Inspiron
Asus
Mac Air
and the servers.xml contain these entries:
Dell Poweredge
HP Tape Backup
Dell Precision
Linux
So what I need the UI to do is whenever the user selects an item from the list, that item should be removed from the other lists so that it can't be selected twice. Note that it should only modify the other lists that are tied to the same list...in other words, if I select 'Mac Air' from the first dropdown, the program should only modify the other two lists and not any of the listboxes tied to the server.xml.
To determine which listbox is bound to which xml file, I use the .Tag property of the listbox when the user control is dynamically created and added to the form.
I would think I can use an ObservableCollection to do this but not sure how to implement it and get it to do what I need.
You can hold a list of all selected computers (or maybe 2 lists, one for servers, one for laptops) at forms level. Your panels (which contain the comboboxes) should subscribe to the listchangeevent of it and adapt the items of the combobox whenever it changes. And vice versa you need to subscribe to the SelectedIndexChanged-event of the comboboxes for maintaining that global list.
In your form:
ObservableCollection<string> selectedServers = new ObservableCollection<string>();
public void Load()
{
List<string> allServers = GetServerNames( "servers.xml" );
foreach( ComputerPanel pnl in serverPanels )
pnl.LoadLists( allServers, selectedServers );
}
In your panel:
public void LoadLists( List<string> allServers, ObservableCollection<string> selectedServers )
{
foreach( string server in allServers )
combo1.Items.Add( server );
selectedServers.CollectionChanged += selectedServers_CollectionChanged;
combo1.SelectedIndexChanged += ( object sender, EventArgs e ) => { selectedServers.Add( (string)combo1.SelectedItem ); };
}
private void selectedServers_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
{
string newlySelectedServer = (string)e.NewItems[0];
if( e.Action == NotifyCollectionChangedAction.Add
&& (string)combo1.SelectedItem != newlySelectedServer ) //only if selector was not my own combo
combo1.Items.Remove( newlySelectedServer );
}
(This code is not foolproof, just to give you an idea)
Related
Good day all. I am trying to accomplish the following:
In a C# WinForm I am having a ComboBox.
In a local data-base I have some "groups" that after execution become folder in "D://" ( they are five )
After that in all the folders I have some files ( the number varies )
I do not know how to populate the ComboBox with the names of those files, and after that when pressing a button I need to interact with the name selected in the ComboBox.
I have absolutely no idea on how to do that. I do not beg for any code ( altho it will be well received ) I just want the guideline ( do "this" first they you can do "that" and at the end you do "that" ) and I will do all the rest. It is just I can not figure that out. Thank you all !
First get the names of the files that is something like this:
string[] files=Directory.GetFiles("//path");
Now you have an array of all file names in the specific folder given above. Now take this string and populate it to the combo box that is something like this.
foreach(string file in files){
comboBox1.Items.add(file);
}
After that you have to create the event behind the combo box. If you drag-drooped combo box, then you can make its event by going to properties. Then code something like this behind the item select event behind combo box.
protected void combobox(bla bla)
{
if(comboBox1.SelectedItem == "An item")
//Do whatever
//it maybe selectedItem or selectedText or something like this
}
I code roughly so it may contain some errors.
Based on the help given I have done:
public string seltest = null;
string group1 = GroupsDBForm.gone;
string[] tests1 =
Directory.GetFiles("D:\\Riddler\\groups\\" + group1).Select(path => Path.GetFileName(path)).ToArray();
foreach (string t1 in tests1)
{
test_list.Items.Add(group1+"\\"+t1);
}
private void begin_test_btn_Click(object sender, EventArgs e)
{
seltest = "D:\\Riddler\\groups\\" + test_list.Text;
Do_Test_Form DoTest = new Do_Test_Form();
DoTest.ShowPath = seltest;
DoTest.MdiParent = this.ParentForm;
DoTest.Show();
}
( Those are the parts of the project connected to the issue, and because they are connected to other parts is might be lessunderstandeble what are the other names mentioned )
I know it is far from the best code but it works. I post it if this help another person with a close to this issue !
Thank you again Jamil!
i am making a book app. i have got the new releases list and the favorites list in the panorama. Now beside every record in the new releases list there is an add to favorite button which add that particular book to the favorite list when clicked and then that particular add to favourite button is removed.`
my favorite list has got the remove button beside every record.
problem.
now when clicking remove me button(of the any particular record) in the favourite list what is the recommended strategy to again show the add to favourite button in the newreleases list which was removed ,real time.
one way is loading the list again which i don't think will be the right move as it is the very first page of the app.
With each item in the new releases and favorites list, assign a unique id.So each item has a unique id while loading on the lists, be it new releases or favorites.
When you tap on add to favorites,all goes good as you say.
Now when you tap on remove from favorites, retrieve the unique id of that ListItem using Listbox.SelectedItem property ( I am considering your ObservableCollection to be a collection of the class Book.cs
private void favoritesListTap(object sender, System.Windows.Input.GestureEventArgs e)
{
Book data = (sender as ListBox).SelectedItem as Book;
int selectedid = data.unique_id;
//Now find that item in the `new releases` list which has the same unique_id as the one we just retrived
foreach( Book bk in newleases.Items)
{
if( bk.unique_id == selectedid)
{
bk.SetFavoriteIcon = "addtofav.png";
break;
}
}
}
use SetFavoriteIcon in Book.cs to set your icon and style with INotifyPropertyChanged event. This will change that one particular list item you want to have the add to favorite button back.
Use the same ItemViewModel for the items in for both lists. Add an IsFavorite bool notifiable property on it and toggle it when an Item gets favorited or un-favorited. Then in the new releases list show the AddToFavorites button only when IsFavorite is false and do the opposite for the favorites list. Also add two Commands in the ItemViewModel named AddToFavoritesCommand and RemoveFromFavoritesCommand that will remove/add the current item from the newreleases/favorites list and toggle the IsFavorite flag respectively.
I'm not very experienced on c#. I'm working with winforms and I'm looking for a way to create something like a list of elements with this template , something like the autocompletion list of visual studio.
Is it possible to do? Shall I use listbox or listview?
EDIT
Sorry my question wasn't clear I don't want to create an autocomplete but what i want to create is something like this a list of things with an icon next to the text of that thing.
As I understand from your question, you can create custom UserControl or create a Form and put ListBox in it. If you use From be sure that you change border style layout, just set it to none. After creation for use it you should create form and show it where you want like this:
FrmAutoComplete x = new FrmAutoComplete();
x.Show();
you can put this form in ToolTipItem and show it.
Good luck.
THis is a quick and dirty example of using images in your Listview control. Since I don;t have a lot of information about what you plan to do, I tried to keep is simple.
In short, you need to load some images into one of the ImageLists (Large or Small) built into the Listview control and assign them keys so that you can assign them to specific list items as you add them.
The trick to this is determining which image to use for a specific list item (assuming there are different images assigned to different list items depending on some differentiating factor. For this example, I used an arbitrary assignment of "cars" or "trucks," and simply decided that the first five items in the list would be cars, and the last five would be trucks. I then assigned each image appropriately, using the image key as I added each listview item. You can do this for more complex scenarios, and when using the image key, it does not matter what order the items are added.
For this use case, you will want to create or use images with dimensions of 16 x 16 pixels. I went ahead and added two images to my project resource file, then simply accessed them using the project Properties.Resources name space. There are other ways to do this as well, but this is the most convenient for me.
Hope that helps.
public partial class Form1 : Form
{
static string CAR_IMAGE_KEY = "Car";
static string TRUCK_IMAGE_KEY = "Truck";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.SetupListview();
this.LoadListView();
}
private void SetupListview()
{
var imgList = new ImageList();
imgList.Images.Add("Car", Properties.Resources.jpgCarImage);
imgList.Images.Add("Truck", Properties.Resources.jpgTruckImage);
var lv = this.listView1;
lv.View = View.List;
lv.SmallImageList = imgList;
}
private void LoadListView()
{
for(int i = 1; i <= 10; i++)
{
string currentImageKey = CAR_IMAGE_KEY;
if(i > 5) currentImageKey = TRUCK_IMAGE_KEY;
var item = this.listView1.Items.Add("Item" + i.ToString(), currentImageKey);
}
}
the problem is probably simple, the post is longer than I wished, but I've tried providing as much info and detail as possible.
I didn't write this GUI app, nor designed, however like most of us I've inherited it.
It had a (regular) ListView, actually the app has several ListView(s), not sure if that matters yet.
Because the # of items arriving to this one ListView (screen/form) can get very large 10K+ I decided to convert it to virtual list, however I'm experiencing some early problems.
One of the biggest problems, is that the items are being populated asynchronously by hitting a button on the form.
When they arrive (from service/network/database) the items are built into ListViewItem(s) and added to someListItems which is an ArrayList.
In my RetrieveVirtualItem method I need to handle both cases when the list is empty and when I already have something (after the button was hit) and that's when I hit the wall (no pun intended)
with the following line of code:
if ( someListItems.Count > e.ItemIndex )
It basically causes (no idea why) a call to Dispose method on the main form which results in the entire application crashing hard. BUT!!, it only happens when I click on the form and list. If the form is just loaded and populated it is fine..the second you left click on the mouse, BOOM!
It took my couple of hours to figure out that the line above was the culprit, as the call stack wasn't very apparent to point that out, and another minute to find out that e.ItemIndex is the culprit. But WHY??? I
n msdn examples they access e.ItemIndex to perform tests and it seems fine.
The Virtual Mode is set in the constructor of the form:
myListView.VirtualMode = true;
VirtualListSize is set right after data arrives asynchronously:
myListView.VirtualListSize = someArrayList.Count;
This is my RetrieveVirtualItem implementation:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// someListItems is an ArrayList that is created when the object/class loads..and populated with ListViewItems.
// i.e. private ArrayList someListItems = new ArrayList();
// it is populated asynchronously by hitting a button on the form, hence it's empty when the form loads..
if ( someListItems.Count <= 0 )
{
e.Item = new ListViewItem( "" );
e.Item.SubItems.Add( "" );
e.Item.SubItems.Add( "" );
}
else
{
// the of code below is the problem, and more specifically - e.ItemIndex causes somehow to call Dispose on the main form..
// the reason I have this code is because if I take it out, all items will show up, no problem, but it will crash when I try to scroll down..
// with message like this:
// Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
if ( someListItems.Count > e.ItemIndex )
{
// took out my code out to eliminate possibility that it's my code. :)
int x = e.ItemIndex * e.ItemIndex;
e.Item = new ListViewItem( x.ToString() );
// but I had something like that just for a test:
// ListViewItem item = ( ListViewItem )someListItems[e.ItemIndex];
// e.Item = item;
// remember that someListItems already has ListViewItems
}
}
}
The method that gets called asynchronously, creates ListViewItems and populates someListItems looks something like that:
private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
{
//Im only showing more essential code..
SomeArrayList.Items.Clear();
myListView.VirtualListSize = ar.Count;
foreach ( SomeObject o in ar )
{
ListViewItem lvi = new ListViewItem( SomeObject.somePropertyID, 0 );
// I've tried changing the above line to: lvi = new ListViewItem( SomeObject.somePropertyID, 0 ); // and having the ListViewItem lvi on the class level. i.e private ListViewItem lvi
// didn't help.. :(
lvi.SubItems.Add( o.someProperty1 );
lvi.SubItems.Add( o.someProperty2 );
// there's quite few of these subitems..2 is enough for this example...
}
// the orignal code, before I changed it to virtual list was adding the items somewhere here..after finished looping, now I'm just trying to reuse that array of ListViewItems.
}
There's also another problem that the items don't really show up at all unless I take out the:
if ( someListItems.Count > e.ItemIndex )
but then I experience the index of out of range issue when I try scrolling.
UPDATE:
I've noticed that if I set the size of virtual list, only after the loop is finished and therefore it is zero (0) at the beginning (I can always reset it to zero), then everything works and don't need to check for size, all I have to do is this:
After the loop in: private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
this.myListView.VirtualListSize = someListItems.Count;
which I would like to thank for Hans Passant for noticing the discrepancy.
So this is the complete, for now (I'm sure that I'll add some code or change as I would like to add some caching, but at least I have something...
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
e.Item = ( ListViewItem )someListItems[e.ItemIndex];
}
The only thing I'm not sure what Hans Passant mentioned is this: "it really isn't okay for this event handler to never allocate a ListViewItem. " which I'm not sure if I understand, because the ListViewItems are allocated and inserted into someListItems array. I do have a try catch around, and I did before as well.
Also, I was thinking and I would appreciate someone's input on this idea:
Create a separate object that would hold all the properies of SomeObject or insert SomeObject(s) into the List and create new ListViewItems as required?
e.g:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// that list would be build sometime during the loop iteration in
// (I'm using the original method name mentioned way above in this post)
// ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
SomeObject o = listOfObjects[e.ItemIndex];
e.Item = new ListViewItem();
e.Item.SubItems.Add(o.prop1);
e.Item.SubItems.Add(o.prop2);
e.Item.SubItems.Add(o.prop3);
}
To answer this question. The virtual list was crashing because the VirtualListSize wasn't being set correctly.
Basically, to help others here, if you have a virtual list, always make sure that the VirtualListSize corresponds to the actual number of items you're trying to show. If not, all hell breaks loose. If you do update, remove, add, anything, you need to reset VirtualListSize to the correct number.
I ended up deriving from ListView and storing my listviewitems in an array.
How can you use a ListView to show the user a collection of objects, and to manage those objects?
For the purpose of argument, here's our design goal: We've got a "monster" object, and that "monster" will have several "powers." The user interacts with the powers via a ListView item.
First, we create a Power object. Give the object the following method:
public ListViewItem makeKey()
{
return new ListViewItem(name);
}
where name is the name of the power, and a string. This ListViewItem will serve as a key, allowing us to identify and retrieve this power later.
Next, we need to add somewhere in the Monster object to keep track of all these powers.
public Dictionary<ListViewItem,Power> powers;
So now we need a way to add powers to the monster.
public void addPower(Power newPower) {
ListViewItem key = newPower.makeKey();
monster.powers.add(key, newPower);
}
Ok, almost done! Now we've got a dictionary of ListViewItems which are tied to the monster's powers. Just grab the keys from that dictionary and stick them in a ListView:
foreach (ListViewItem key in powers.Keys)
powerList.Items.Add(key);
Where powerList is the ListView we're adding the ListViewItems to.
Alright, so we've got the ListViewItems in the ListView! Now, how do we interact with those? Make a button and then a function something like this:
private void powerRemoveButton_Click(object sender, EventArgs e)
{
if (powerList.SelectedIndices.Count > 0)
{
int n = powerList.SelectedIndices[0];
ListViewItem key = powerList.Items[n];
monster.powers.Remove(key);
powerList.Items.Remove(key);
}
else
{
MessageBox.Show("No power selected.");
}
}
And that's that. I hope you've found this helpful. I'm not sure if this was an intentional aspect of their design, but ListViews and Dictionaries blend together so amazingly well when you use a ListViewItem as a key that it's a joy!