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
Related
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.
So I'm working on learning LINQ and was assigned to work with two .txt files and to join them.
So far I'm doing well, but I've reached a bit of an impasse with the display. I'm supposed to have the name display once and then the following cases that are closed have only the case information.
The issue I'm having is that the name keeps repeating after the dataset is listed in the ListView. I think there is something wrong with the LINQ statement or the way I'm going through the foreach loop. Here is the code for the main form below:
//Fills the lists by calling the methods from the DB classes
techs = TechnicianDB.GetTechnicians();
incidents = IncidentDB.GetIncidents();
//Creates a variable to use in the LINQ statements
var ClosedCases = from Incident in incidents
join Technician in techs
on Incident.TechID equals Technician.TechID
where Incident.DateClosed!= null
orderby Technician.Name, Incident.DateOpened descending
select new { Technician.Name, Incident.ProductCode, Incident.DateOpened, Incident.DateClosed, Incident.Title };
//variables to hold the technician name, and the integer to increment the listview
string techName = "";
int i = 0;
//foreach loop to pull the fields out of the lists and to display them in the required areas in the listview box
foreach (var Incident in ClosedCases)
{
foreach (var Technician in ClosedCases)
{
if (Technician.Name != techName)
{
lvClosedCases.Items.Add(Technician.Name);
techName = Technician.Name;
}
else
{
lvClosedCases.Items.Add("");
}
}
lvClosedCases.Items[i].SubItems.Add(Incident.ProductCode);
lvClosedCases.Items[i].SubItems.Add(Incident.DateOpened.ToString());
lvClosedCases.Items[i].SubItems.Add(Incident.DateClosed.ToString());
lvClosedCases.Items[i].SubItems.Add(Incident.Title);
i++;
}
And here is the result I get: Result
As can be seen by the bar on the right hand side, the list continues on for several more columns.
What am I missing here?
Thank you.
EDIT: Per request, here is what the results are supposed to look like:
The example I was given
Why you are iterating closed cases twice?
foreach (var Incident in ClosedCases)
{
foreach (var Technician in ClosedCases)
{
}
}
I have searched and searched for an answer to this problem. I thought I came close by finding this post:
Get object information of selected listbox item
But I keep getting a an "invalid cast exception" error at this part in the code:
string m = ((Customer)LBox.SelectedItem).ID;
Here is most of my code thus far, where I add the listbox items from a Dataclass (linq to SQL). Where I am stuck is that I'd like to get the DB_ID of the record that I have selected in the Listbox.
I have a workaround where I can do another SQL query on the DB with the customer's name that is the LBox.SelectedItem value, but i figure I must be doing something wrong to not be able to cast the SelectedItem value back to Customer to get the ID?
I am just starting out in C# and know practically nothing, so any help would be much appreciated.
DataClasses1DataContext dc = new DataClasses1DataContext(Properties.Settings.Default.DBConnectionString);
public MainWindow()
{
InitializeComponent();
var q = from accts in dc.Customers where accts.SysStatus == "Active" orderby accts.Customer select accts;
if (dc.DatabaseExists())
foreach (Customer c in q)
LBox.Items.Add(c.Customer);
}
public void LBox_SelectionChanged (object sender, SelectionChangedEventArgs e)
{
lblComp.Content = LBox.SelectedItem;
string m = ((Customer)LBox.SelectedItem).ID;
}
I think #Boxed help me out here.
I modified the code as follows:
foreach (Surgeon c in q)
LBox.Items.Add(c);
LBox.DisplayMemberPath = "Customer";
Then I was able to obtain the values from the Customers Class as such:
string m = ((Customers)LBox.SelectedItem).ID;
lblComp.Content = ((Customers)LBox.SelectedItem).Customer;
txtDetails.Text = m;
Is this code ideal, or should I be doing something else?
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!!
I have a collection of objects where each object contains another collection of objects. I need to find out the fastest way to check if it contains all values of List<string>.
Here is an example:
class Video {
List<Tag> Tags;
}
class Tag{
public Tag (string name){
Name = name;
}
string Name;
}
List<string> selectedTags = new List<string>();
selectedTags.Add("Foo");
selectedTags.Add("Moo");
selectedTags.Add("Boo");
List<Video> videos = new List<Video>();
// Case A
Video videoA = new Video();
videoA.Tags = new List<Tag>();
videoA.Tags.Add(new Tag("Foo"));
videoA.Tags.Add(new Tag("Moo"));
videos.Add(videoA);
videoA should not be selected by LINQ because it doesn't contain all tags.
// Case B
Video videoB = new Video();
videoB.Tags = new List<Tag>();
videoB.Tags.Add(new Tag("Foo"));
videoB.Tags.Add(new Tag("Moo"));
videoB.Tags.Add(new Tag("Boo"));
videos.Add(videoB);
videoB should be selected by LINQ because it contains all tags.
I tried this with foreach loops, but it's too slow so I'm looking for a LINQ solution.
foreach (Video video in videos) {
if (video.Tags.Count() > 0) {
bool containAllTags = true;
foreach (string tagToFind in selectedTags) {
bool tagFound = false;
foreach (Tag tagItem in video.Tags) {
if (tagToFind == tagItem.Name)
tagFound = true;
}
if (!tagFound)
containAllTags = false;
}
if (containAllTags)
result.Add(videoItem);
}
}
The resulting LINQ should look like this:
IEnumerable<Video> = from vid in videos
where vid.Tags.( ..I dont know.. )
select vid;
I tried several ways with .Any, .All, etc.. but I can't find the solution and I can't use .Intersect because one is a List of strings and the other is a List of objects. Note that in the production version, Video and Tag elements have many more properties.
With your current code, you logically want:
IEnumerable<Video> result = from vid in videos
where selectedTags.All(tag =>
vid.Tags.Any(t => t.Name == tag))
select vid;
Or equivalently:
var result = videos.Where(vid => selectedTags.All(tag =>
vid.Tags.Any(t => t.Name == tag)));
This is assuming you've made Tag.Name and Video.Tags public, of course - ideally as properties rather than as fields.
Note how we're calling All against selectedTags, as (assuming I've read your requirements correctly) it's important that all the selected tags are present in the video - it's not important that all the video's tags are selected.
Now that could be relatively slow, if you have a lot of tags to check and a lot of tags per video.
However, knowing how to optimize it will really depend on some other choices:
If the order of the tags isn't important, could you change Video.Tags to be a set instead of a list?
Are you always looking through the same set of videos, so you could perform some pre-processing?
Is the total number of tags available large? What about the number of tags per video? What about the number of selected tags?
Alternatively, you can project each video to its "list of tags" and check whether there are any in the selected set which aren't in the video's set:
var result = videos.Where(vid => !selectedTags.Except(vid.Tags.Select(t => t.Name))
.Any());