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!
Related
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)
I have some strange behaviour I can't explain:
I have a List of Ellipsis in a WPF-Canvas, which I'd like to make blink.
For performances purpose, I add newly created stars to the Canvas (which holds more objects) and to a local list. This means I can work just with the local List and dont have to recheck the Children-Collection every time.
My code is looking like this:
private const Int32 STAR_COUNT = 500;
private readonly double STAR_MOVE_RATE = GameObject.OBJECTS_MOVE_RATE/2;
private readonly Timer _blinkingTimer = new Timer();
private readonly Canvas _gameField;
private readonly List<Ellipse> _stars = new List<Ellipse>();
public StarHandler(Canvas gameField)
{
_gameField = gameField;
_blinkingTimer.Interval = 70;
_blinkingTimer.Elapsed += _blinkingTimer_Elapsed;
for (int i = 0; i < STAR_COUNT; i++)
AddSetStar(StarFactory.CreateStar());
_blinkingTimer.Start();
}
private void AddSetStar(Ellipse star)
{
Canvas.SetTop(star, GameObject.Random.Next(GameObject.LEVEL_HEIGHT));
Canvas.SetLeft(star, GameObject.Random.Next(GameObject.LEVEL_WIDTH));
Panel.SetZIndex(star, 0);
_gameField.Children.Add(star);
_stars.Add(star);
}
private void _blinkingTimer_Elapsed(object sender, ElapsedEventArgs e)
{
ThreadingHelper.ThreadingAction(() =>
{
Ellipse star = _stars.ElementAt(GameObject.Random.Next(STAR_COUNT));
_gameField.Children.Remove(star);
_stars.Remove(star);
var newStar = StarFactory.CreateStar();
AddSetStar(newStar);
});
}
The StarFactory just creates such an Ellipse and returns it.
What I dont get: The
_gameField.Children.Remove(star);
works only for the Stars I add in the Constructor. The Stars I add on the Timer are not found on the Children-List.
I really have no idea, how this Elements can not be found since I add newly created Stars everytime on both lists.
P.S.: The ThreadingAction is just a helper to work on the UI-Thread:
internal static void ThreadingAction(Action action)
{
if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
action();
else
Application.Current.Dispatcher.Invoke(action);
}
Edit: The reason why I'm holding two lists is this:
If I want to work with the List, I use:
_stars.ForEach(f =>
Without this, I'd have to
_gameField.Children.OfType<Ellipse>().ToList().ForEach(f => )
This is 2 Enumerations, one for OfType and one for ToList.
And what If I add other ellipses, which are not Stars?
About the Construction of the Stars: I do this only on two positions: In the Timer-Elapsed Event and in the Constructor. Does the Children-List not point to the objects then?
So the Stars are the same references, since I add the same reference everytime in both Lists.
You have a number of problems with your code. I shall attempt to address each in turn. First, you said that you
add newly created stars to the Canvas (which holds more objects) and to a local list. This means I can work just with the local List and dont have to recheck the Children-Collection every time
However, there is no benefit to doing this. The Children collection points to a certain location in memory and your collection points to another... virtually no difference.
What I dont get: The Children.Remove(star) works only for the Stars I add in the Constructor
That is because you can only remove an item from the collection if that exact item is in the collection. Therefore, you cannot generate a new object with certain property values and expect that to equal another object with identical property values... they are different objects in memory. So, to get around this, you can use LinQ to do something like this:
_gameField.Children.Remove(_gameField.Children[0]);
However, I believe that your final error is that you shouldn't be removing and re-adding these objects into the Children collection as you are anyway. Instead, it makes more sense to leave the objects in the Children collection and merely change their Visiblity values from Visible to Hidden and back repeatedly... perhaps something like this:
foreach (UIElement element in Canvas.Children)
{
element.Visibility = element.Visibility == Visibility.Hidden ?
Visibility.Visible : Visibility.Hidden;
}
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);
}
}
I have a MyListView class that is inheriting from ListView, and is overriding OnDragDrop() (and the other necessary events to implement drag and drop). When I place two of these MyListviews on a form I am able to drag an item from one of them and drop it to the other one. This part works.
Now I want to override OnDoubleClick() to that class such that again if I place two of these MyListViews on a form and double clicked on one of them, the item gets removed from that and gets added to the other one. But I can't get my head around how to do this one.
Could you please give me some ideas? Thanks.
Don't know if you manage the sleection of the item in a particular way, but you can
or after handling double-click look for SelectedItems and act on it
or you can add a code like this using ListViewHitTestInfo class:
private override OnDoubleClick(...)
{
ListViewHitTestInfo hit = this.HitTest(e.Location);
if (hit.Item != null)
{
ListViewItem doubleClickedItem = hit.Item;
}
}
Put the logic in your host form by:
Handle double-click of first ListView
Remove from first ListView
Add to second ListView
Unless you are doing this in many different forms - it's not worth complicating it more than that.
EDIT:
If justified, centralizing can be as easy as adding a method which does the same thing (pseudocode)
public void MyForm_OnListViewDoubleClick(object sender, EventArgs e)
{
MoveListItem(firstListView, secondListView);
}
// ...
public static void MoveListItem(ListView source, ListView destination)
{
var listItem = source.SelectedItem;
source.Remove( listItem );
destination.Add( listItem );
}
Here's the answer to your title
protected override void OnDoubleClick(EventArgs e)
{
base.OnDoubleClick(e);
}
And here is the answer to your question
Using DoubleClick event on a inherited class from ListView
This just links back to your other, very similar question.
Is there a straighforward way to set additional text to appear in a tooltip when a user's mouse is held over an item in a CheckedListBox?
What I would expect to be able to do in code is:
uiChkLstTables.DisplayOnHoverMember = "DisplayOnHoverProperty"; //Property contains extended details
Can anyone point me in the right direction to do this? I've already found a couple of articles that involve detecting which item the mouse is currently over and creating a new tooltip instance, but this sounds a little too contrived to be the best way.
Thanks in advance.
Add a Tooltip object to your form and then add an event handler for the CheckedListBox.MouseHover that calls a method ShowToolTip();
Add MouseMove event of your CheckedListBox which has the following code:
//Make ttIndex a global integer variable to store index of item currently showing tooltip.
//Check if current location is different from item having tooltip, if so call method
if (ttIndex != checkedListBox1.IndexFromPoint(e.Location))
ShowToolTip();
Then create the ShowToolTip method:
private void ShowToolTip()
{
ttIndex = checkedListBox1.IndexFromPoint(checkedListBox1.PointToClient(MousePosition));
if (ttIndex > -1)
{
Point p = PointToClient(MousePosition);
toolTip1.ToolTipTitle = "Tooltip Title";
toolTip1.SetToolTip(checkedListBox1, checkedListBox1.Items[ttIndex].ToString());
}
}
Alternately, you could use a ListView with checkboxes instead. This control has
builtin support for tooltips.
Contrived or not; that's what there is...
I'm not aware of an easier way than you have already described (although I'd probably re-use a tooltip instance, rather than creating new all the time). If you have articles that show this, then use them - or use a 3rd party control that supports this natively (none leap to mind).
I would like to expand upon Fermin's answer in order to perhaps make his wonderful solution slightly more clear.
In the form that you're working in (likely in the .Designer.cs file), you need to add a MouseMove event handler to your CheckedListBox (Fermin originally suggested a MouseHover event handler, but this did not work for me).
this.checkedListBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.showCheckBoxToolTip);
Next, add two class attributes to your form, a ToolTip object and an integer to keep track of the last checkbox whose tool tip was shown
private ToolTip toolTip1;
private int toolTipIndex;
Finally, you need to implement the showCheckBoxToolTip() method. This method is very similar to Fermin's answer, except that I combined the event callback method with the ShowToolTip() method. Also, notice that one of the method parameters is a MouseEventArgs. This is because the MouseMove attribute requires a MouseEventHandler, which then supplies MouseEventArgs.
private void showCheckBoxToolTip(object sender, MouseEventArgs e)
{
if (toolTipIndex != this.checkedListBox.IndexFromPoint(e.Location))
{
toolTipIndex = checkedListBox.IndexFromPoint(checkedListBox.PointToClient(MousePosition));
if (toolTipIndex > -1)
{
toolTip1.SetToolTip(checkedListBox, checkedListBox.Items[toolTipIndex].ToString());
}
}
}
Run through your ListItems in your checkbox list of items and set the appropriate text as the item 'title' attribute, and it will display on hover...
foreach (ListItem item in checkBoxList.Items)
{
//Find your item here...maybe a switch statement or
//a bunch of if()'s
if(item.Value.ToString() == "item 1")
{
item.Attributes["title"] = "This tooltip will display when I hover over item 1 now, thats it!!!";
}
if(item.Value.ToString() == "item 2")
{
item.Attributes["title"] = "This tooltip will display when I hover over item 2 now, thats it!!!";
}
}