Find if a ListViewGroup already exists - c#

Classic Windows form type of interface, I have two ListViews, one on the left (SongsAvailable) and one on the right (SongsInLibrary). Select an entry on the list on the left, click a button to add it to the list on the right, but put it in the right group (if it exists.) I have this code
private void AddSelected(Object sender, EventArgs e)
{
bool rc = false;
foreach (ListViewItem item in SongsAvailable.SelectedItems)
{
var s = item.SubItems[0].Text.Substring(0, 1);
TestGroup = new ListViewGroup(s, s);
rc = SongsInLibrary.Groups.Contains(TestGroup);
if (!rc) { // create a new group and add it }
SongsInLibrary.Items.Add(new ListViewItem(new[] { item.SubItems[0].Text, item.SubItems[1].Text, item.SubItems[2].Text, item.SubItems[3].Text }, ListGroup ));
}
}
The return rc is always false (which doesn't surprise me, a new ListViewGroup can't already exist in the Groups collection) so I always wind up adding new groups. Documentation on "Contains" is also frustratingly terse. Short of iterating through all the groups, how do I find if A group already exists?

I don't know much about these ListViewGroup and ListViewItem classes, but since no one else has answered, here's at least one way to do it:
Use the Cast method (from System.Linq) to cast the ListViewGroupCollection to an IEnumerable<ListViewGroup>
Get the FirstOrDefault group that has the header we're looking for
If the group doesn't exist, create a new one and add it to SongsInLibrary.Groups
Remove the item from SongsAvailable (which appears to be required in order to add it to a new group)
Set the item's Group to the group we want it in
Add the item to our SongsInLibrary collection
Doing it this way avoids the problem with calling Contains with a new Group (that will never exist)
For example:
foreach (ListViewItem item in SongsAvailable.SelectedItems)
{
// Determine the group we want to add this to (the first letter of the item)
var groupHeader = item.Text.Substring(0, 1);
// Get the first group that matches, or null if it's not there
var group = SongsInLibrary.Groups.Cast<ListViewGroup>()
.FirstOrDefault(g => g.Header == groupHeader);
// If it's not there, create it and add it
if (group == null)
{
group = new ListViewGroup(groupHeader);
SongsInLibrary.Groups.Add(group);
}
// Move the song to the goup and add the song to the library
SongsAvailable.Items.Remove(item);
item.Group = group;
SongsInLibrary.Items.Add(item);
}
There may be a better way, but from the quick glance at the documentation for ListView.Groups, they don't seem overly simple to work with.

Related

WinForms MultiSelectTreeView - EnsureVisible() not working

After removing one TreeNode I want the View to be at the node after the one I deleted (the same functionality should later also be implemented for editing a node), but currently it always shows the top of the list again and I need to scroll down manually, which can be quite irritating for the user.
I'm using the EnsureVisible() method but unfortunately it doesn't work (I'm testing it with a TreeView which contains about 30 nodes that don't have sub-nodes).
The code of the function (Only the first line and the last 4/5 lines are relevant, I think):
public override void Remove()
{
TreeNode moveToThisNode = treeControl1.SelectedNodes.Last().NextVisibleNode;
// first, group them by whether they are ObjectGroups or ObjectTypes
var grouping = from node in treeControl1.SelectedNodes
where !node.IsUngroupedNode()
let isGroup = node.IsGroupNode()
group node by isGroup into g
select new { IsGroupNode = g.Key, Items = g };
foreach (var grp in grouping)
{
foreach (var selectedNode in grp.Items)
{
// Only allow removal FIRSTLY of ObjectGroups and SECONDLY that are NOT the "ungrouped" group.
if (grp.IsGroupNode)
{
// Removes the Group
var cmd = (Commands.DataCommand<string>)mApplicationData.CommandFactory.Create(string.Concat(CommandPrefix, "Remove"));
cmd.Data = selectedNode.Text;
cmd.Execute();
}
else // ObjectType node --> just move to ungrouped
{
var t = (ObjectType)selectedNode.Tag;
var parentNode = selectedNode.Parent;
if (parentNode?.IsGroupNode() == true &&
parentNode?.IsUngroupedNode() == false) // No removal of ObjectTypes from within "ungrouped"
{
var group = (ObjectGroup)parentNode.Tag;
// Remove the ObjectType from the ObjectGroup but does not delete it -> becomes "ungrouped".
var cmd = (Commands.IGroupTypeCommand)mApplicationData.CommandFactory.Create(string.Concat(CommandPrefix, "TypeRemove"));
cmd.ObjectClass = t.Class;
cmd.ObjectTypeName = t.Name;
cmd.Data = group.Name;
cmd.Execute();
}
}
}
}
UpdateData();
if (moveToThisNode!=null)
{
moveToThisNode.EnsureVisible();
MessageBox.Show("Dummy test if moveToThisNode isn't null");
}
}
I figured it out!
The problem was that after the Remove() function my UpdateData() function is called which redraws all nodes. So calling EnsureVisible() before that is total nonsense.
So what I did was that in the Remove() I stored the name of the node I want to jump to in a member variable and at the end of UpdateData() I get the node with that name out of the TreeNodeCollection and (FINALLY) call EnsureVisible() for it.

Why is RemoveAll(x => x.Condition) removing all my records?

I'm working on creating a filter for a collection of employees. In order to do this I initially fetch a raw collection of all employees. I clone this list so I can iterate over the original list but remove items from the second list.
For each filter I have, I build a collection of employee ids that pass the filter. Having gone through all filters I then attempt to remove everything that isn't contained in any of these lists from the cloned list.
However for some reason, whenever I attempt to do this using .RemoveAll(), all records seemed to be removed and I can't figure out why.
Here is a stripped down version of the method I'm using, with only 1 filter applied:
public List<int> GetFilteredEmployeeIds(int? brandId)
{
List<int> employeeIds = GetFilteredEmployeeIdsBySearchTerm();
List<int> filteredEmployeeIds = employeeIds.Clone();
// Now filter the results based on which checkboxes are ticked
foreach (var employeeId in employeeIds)
{
// 3rd party API used to get values - please ignore for this example
Member m = new Member(employeeId);
if (m.IsInGroup("Employees"))
{
int memberBrandId = Convert.ToInt32(m.getProperty("brandID").Value);
// Filter by brand
List<int> filteredEmployeeIdsByBrand = new List<int>();
if (brandId != null)
{
if (brandId == memberBrandId)
filteredEmployeeIdsByBrand.Add(m.Id);
var setToRemove = new HashSet<int>(filteredEmployeeIdsByBrand);
filteredEmployeeIds.RemoveAll(x => !setToRemove.Contains(x));
}
}
}
return filteredEmployeeIds;
}
As you can see, I'm basically attempting to remove all records from the cloned record set, wherever the id doesn't match in the second collection. However for some reason every record seems to be getting removed.
Anybody know why?
P.S: Just to clarify, I have put in logging to check the values throughout the process and there are records appearing in the second list, however for whatever reason they're not getting matched in the RemoveAll()
Thanks
Ok only minutes after posting this I realised what I did wrong: The scoping is incorrect. What it should've been was like so:
public List<int> GetFilteredEmployeeIds(int? brandId)
{
List<int> employeeIds = GetFilteredEmployeeIdsBySearchTerm();
List<int> filteredEmployeeIds = employeeIds.Clone();
List<int> filteredEmployeeIdsByBrand = new List<int>();
// Now filter the results based on which checkboxes are ticked
foreach (var employeeId in employeeIds)
{
Member m = new Member(employeeId);
if (m.IsInGroup("Employees"))
{
int memberBrandId = Convert.ToInt32(m.getProperty("brandID").Value);
// Filter by brand
if (brandId != null)
{
if (brandId == memberBrandId)
filteredEmployeeIdsByBrand.Add(m.Id);
}
}
}
var setToRemove = new HashSet<int>(filteredEmployeeIdsByBrand);
filteredEmployeeIds.RemoveAll(x => !setToRemove.Contains(x));
return filteredEmployeeIds;
}
Essentially the removal of entries needed to be done outside the loop of the employee ids :-)
I know that you said your example was stripped down, so maybe this wouldn't suit, but could you do something like the following:
public List<int> GetFilteredEmployeeIds(int? brandId)
{
List<int> employeeIds = GetFilteredEmployeeIdsBySearchTerm();
return employeeIds.Where(e => MemberIsEmployeeWithBrand(e, brandId)).ToList();
}
private bool MemberIsEmployeeWithBrand(int employeeId, int? brandId)
{
Member m = new Member(employeeId);
if (!m.IsInGroup("Employees"))
{
return false;
}
int memberBrandId = Convert.ToInt32(m.getProperty("brandID").Value);
return brandId == memberBrandId;
}
I've just done that off the top of my head, not tested, but if all you need to do is filter the employee ids, then maybe you don't need to clone the original list, just use the Where function to do the filtering on it directly??
Please someone let me know if i've done something blindingly stupid!!

Updating a list using Linq

I am creating two lists of objects. One "new" list, one "old list".
I want to take the value of one property from an object on the new list and set the property on the old list on the matching object the the new value.
//Original Solution
foreach (RyderQuestion quest in myList)
{
//compare it to every question in ryder questions
foreach (RyderQuestion oldQuestion in _ryderQuestions)
{
//if the question ids match, they are the same question, and should have the right selected option
//selecting the option sets the checkbox of the MultipleChoideQuestionControl
if (oldQuestion.QuestionID == quest.QuestionID)
{
oldQuestion.SelectedOption = quest.SelectedOption;
}
}
}
I am trying to convert it to LINQ to make it more effecient using joins, but how do i update the value directly?
var x = from quest in myList
join oldquest in _ryderQuestions
on new { quest.QuestionID, quest.ShowOn, quest.QuestionOrder }
equals new { oldquest.QuestionID, oldquest.ShowOn, oldquest.QuestionOrder }
select oldquest.SelectedOption = quest.SelectedOption;
This query returns the values to the x list, but I want to actually update the object in the old list instead.
Linq is for querying, not updating. You can join the two lists to line up the object to update, but you'll still have to loop to make the changes:
var query = from quest in myList
join oldquest in _ryderQuestions
on new { quest.QuestionID, quest.ShowOn, quest.QuestionOrder }
equals new { oldquest.QuestionID, oldquest.ShowOn, oldquest.QuestionOrder }
select new {oldquest, quest};
foreach(var item in query}
item.oldquest.SelectedOption = item.quest.SelectedOption
For example:
var x = from quest in myList
join oldquest in _ryderQuestions
on new { quest.QuestionID, quest.ShowOn, quest.QuestionOrder }
equals new { oldquest.QuestionID, oldquest.ShowOn, oldquest.QuestionOrder }
select new {quest , oldquest};
foreach(var item in x)
{
item.quest.SelectedOption = item.oldquest.SelectedOption;
}
You mean this?

How to code add to linked list so you can print from the oldest to newest item?

Okay so I have to print out a linked list from the order I put it in. Each node refers to a ticket object, and the ticket object has its own print function that I can call upon. I can remove the start of the list and refer it to the next ticket, but have it coded so that it prints out the newest to oldest. I believe the problem lies in my code that allows me to add a ticket to the list:
private class TicketNode
{ //basic node
public TicketNode next;
public Ticket data;
public TicketNode(Ticket tic)
{
data = tic;
}
}
public void PrintAll()
{//Prints all tickets
TicketNode cur = first;
while (cur != null)
{
cur.data.PrintDescription();
cur = cur.next;
}
}
public void AddTicket(Ticket t)
{
TicketNode ticNode; //creates a new node
if (first == null) //for kick-starting the list
first = new TicketNode(t);
else
{
ticNode = new TicketNode(t); //initializes node
ticNode.next = first;
first = ticNode; //first.next is the ticket that was ticNode
}
}
ex: I put in the tickets with strings "Low", "Another Low", and "Final Low" and when I want to print it out I expect:
Low
Another Low
Final Low
Instead I get:
Final Low
Another Low
Low
If I were to remove to oldest ("Low") I should see something like this next time print:
Another Low
Final Low
Any ideas on a how to reorient the list?
The simplest solution would be to insert new items at the end of the list. To do that in O(1), you need to keep a pointer last to the last item in the list. When you insert a new item, you use that pointer to quickly get the last item, append the new item and update the pointer.
With that modification, you can iterate from first via next and actually get the items in their insert order.
While Adding element into linked list, you have find end of the list and there. Following code might be useful for you
AddTicket method is sholud be like this
void AddTicket(Ticket t)
{
TicketNode ticNode; //creates a new node
if (first == null) //for kick-starting the list
first = new TicketNode(t);
else
{
ticNodeNew = new TicketNode(t);
TicketNode ticNode; = first;
while(ticNode.next != null)
{
ticNode = ticNode.next;
}
ticNode.next = ticNodeNew;
}
}
}
In your linked list, the an item references the next oldest, etc. The most recent is first in the list, and the oldest items is at the end of the list. That is why when you run through the list in PrintAll() you get items youngest to oldest.
You need to print your list out in reverse order and an easy way of doing that is to use a Stack.
public void PrintAll()
{
var stack = new Stack<TicketNode>();
TicketNode cur = first;
while (cur != null)
{
stack.Push(cur);
cur = cur.next;
}
while (stack.Count > 0)
stack.Pop().data.PrintDescription();
}
MH09's solution which stores the list in oldest to youngest order is also valid. MH09's solution traverses the entire list on AddTicket(), my solution traverses the list in PrintAll(). You might want to choose which solution is better suited to you on the basis of performance. However in both cases the traversal is O(n).

Accessing multiple CheckedItems as they are checked

I am trying to filter documents based on selected tags in a checkedlistbox -- it is populated with objects of my class Tag -- but am unable to access the items in order to search. I have tried a couple of variations but the method I am using just now is:
private void chlbTags_ItemCheck(object sender, ItemCheckEventArgs e)
{
List<Tag> chosenTags = new List<Tag>();
foreach (object item in chlbTags.CheckedItems)
{
chosenTags.Add((Tag)item);
}
fillDocs(tags: chosenTags);
}
I know it is probably something simple but all I seem to find when I search seems to be related to getting strings back.
EDIT: chosenTags is always null no matter how many tags are checked.
EDIT 2: Thanks to #Jony A damn it... this has been partly sorted. But now I can't check more than one tag without throwing an InvalidCastException.
EDIT 3: How the checked listbox is populated.
public static List<Tag> fillUsed(List<int> docIds = null)
{
List<Tag> used;
if (docIds == null)
{
used = (from t in frmFocus._context.Tags
where t.AllocateDocumentTags.Count > 0
select t).ToList();
}
else
{
used = (from id in docIds
join adt in frmFocus._context.AllocateDocumentTags on
id equals adt.documentId
join t in _tags on adt.tagId equals t.id
select t).ToList();
}
return used;
}
Any help is appreciated, thanks.
This portion works
public void fillDocs(List<Tag> tags = null)
{
lvDownload.Items.Clear();
if (tags != null)
{
docs = docManagement.fillUp(tags: tags);
}
else
{
docs = docManagement.fillUp();
}
}
The code you posted should fail with a NullReferenceException.
You should replace List<Tag> chosenTags = null; with List<Tag> chosenTags = new List<Tag>();
It should be fine then...
Like Jony stated
This code will fail you have to do more than just assign null to the object.. you need to do what they call "NEWING" the object meaining the key word new
I am trying to filter documents based on selected tags in a checkedlistbox -- it is populated with objects of my class Tag -- but am unable to access the items in order to search. I have tried a couple of variations but the method I am using just now is:
this will work if you change it.
private void chlbTags_ItemCheck(object sender, ItemCheckEventArgs e)
{
List<Tag> chosenTags = new List<Tag>();
foreach (object item in chlbTags.CheckedItems)
{
Tag tag = (Tag) item.Tag;
chosenTags.Add(tag);
-- your code chosenTags.Add((Tag)item);
}
fillDocs(tags: chosenTags);
}
Casting has to be done by getting at the string property
// checkBox is CheckBox
string s = checkBox.Tag.ToString();
you can use something like this to test an individual item or items as well if you like

Categories

Resources