Implementing a find system for a ListView in C# - c#

I am creating an application in C# using a ListView control that lets you create lists. I am implementing a Find function using the Find() method. Here’s my code:
if (findTextBox.Text != "")
{
ListViewItem[] lviFoundList = listItemsList.Items.Find(findTextBox.Text, true);
amountFound.Text = "Found " + Convert.ToString(lviFoundList.Count());
if (lviFoundList.Count() != 0)
{
int firstItemIndex = lviFoundList[0].Index;
listItemsList.Items[firstItemIndex].Selected = true;
}
}
else
{
amountFound.Text = "Found 0";
}
However, it doesn’t return any matches. What am I doing wrong?

Find method requires your listView item's Name, did you set your list view item's name property? If you want to search for text you can use this:
var lviFoundList = new List<ListViewItem>();
foreach(var item in listItemsList.Items)
{
if(item.Text == findTextBox.Text) lviFoundList.Add(item);
}

The Find() Method looks at the ListViewItem's name, not it's text.
You want this instead:
if (findTextBox.Text != "")
{
List<ListViewItem> items = new List<ListViewItems>();
foreach ListViewItem lvi in listItemsList.Items
{
if (lvi.Text == findTextBox.Text)
items.Add(lvi);
}
amountFound.Text = "Found " + Convert.ToString(lviFoundList.Count());
if(lviFoundList.Count() != 0)
{
int firstItemIndex = lviFoundList[0].Index;
listItemsList.Items[firstItemIndex].Selected = true;
}
}
else
{
amountFound.Text = "Found 0";
}

Honestly, the ListView.Find() method is rather poor and it's much easier to roll your own with LINQ. Think about what Find really is trying to accomplish -- a specific filtering, typically one record.
The first step, if you haven't already, would be to keep a cached collection of your data objects. Let's assume you have a list of Person classes like so:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public DateTime DateOfBirth { get; set; }
}
Then in your MainForm you have a ListView and a member variable people defined as a List<Person>. Your ListView.Items should reflect the contents of this List<Person>.
So now maybe you want to find a person based on their FirstName or LastName, right? You could use LINQ in a function like so:
int FindFirstIndexOfPersonNamed(string firstOrLastName)
{
// WARNING: This is case sensitive!
return people.FindIndex(p => p.FirstName.Contains(firstOrLastName) || p.LastName.Contains(firstOrLastName));
}
Since your ListView.Items should be reflecting your List<Person> the index should be identical:
// Get the found item and do whatever you want with it...
var selectedListViewItem = listView.Items[index];

Related

How to combine many properties into one List<string> property in C#

I am a little bit confused about how to read data from Excel. I am trying to import Excel for updating product list, I create an Excel model; I added all basic properties like name, price, quantity, etc. into this model. I will read all Excel and map into this model. That's ok, then I will give this model to EF Core 5 to save to SQL Server.
public class ExcelModel
{
public string Name { get; set }
public int Price { get; set }
public int Quantity { get; set }
}
I have a problem with product options. According to my DB schema, I have one table of products, one for options, one for option values, one for productOptionRelation.
Can you suggest another solution way or just solve on my way?
My colleges did this created field corresponding to values. like option1 and optionValue1, option2 and optionValue2 many of them, because each product could have many options. Model look like that, 20 option and 20 value was declared here and they manually map all these
For a temporary solution, I limited this option up to 5 and I created an list. and encapsulate all of them into list
public class ExcelOptionViewModel
{
public string Option { get; set; }
public string Value { get; set; }
}
This is my temp model, I encapsulated like that.
public IList<ExcelOptionViewModel> OptionModels { get; set; } = new List<ExcelOptionViewModel>();
public string Option1
{
get { return OptionModels[0].Option; }
set
{
this.OptionModels.Insert(0, new ExcelOptionViewModel { Option = value });
}
}
public string Option1Value
{
get { return OptionModels[0].Value; }
set { this.OptionModels[0].Value = value; }
}
This would be unlimited, You should enter how much you want
I have 2 solutions still I am researching one is, creating a method inside the excelviewmodel, this method will add all options and values into a list or I will use reflection, I am looking something like underlying type I will all option and values this underlying base type or something, when property loop came here, checking the type and assign all option1,option2,option3 or name like that properties to List<string> options, and same for the option values. I will use reading like option[0] and optionvalue[0]
Excel column names must be different because I read excel and turn it into datatable. Datatable column names must be different, it's not valid for reading into datatable
I used basically excel to data table function I can't remember but probably I found it in StackOverflow. Also, I added a feature there If some cell is null it will miss.
public List<T> ConvertDataTableToList<T>(DataTable dt)
{
//datatable clomun names
var columnNames = dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName.ToLower()).ToList();
//selection properties equals to columnnames because I dont want loop for all props
var properties = typeof(T).GetProperties().Where(prp => columnNames.Any(t => t.ToLower() == prp.Name.ToLower()));
return dt.AsEnumerable().Select(row =>
{
var objT = Activator.CreateInstance<T>();
foreach (var pro in properties)
{
try
{
if (row[pro.Name] != DBNull.Value)
pro.SetValue(objT, row[pro.Name], null);
else
pro.SetValue(objT, null, null);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
return objT;
}).ToList();
}
I am looking something here when option1 or option2 comes here it would put this into a list
Also in my dt to model converter I dont want to use If but if some data value is null It throws an error which cant convert from dbnull value. If you have a suggest for it I would like release if condition :)
When All done I will map this excelviewmodel to product model something like this
foreach (var prop in SideParams.columns)
{
var source = row.GetType().GetProperty(prop);
var destination = product.GetType().GetProperty(prop);
if (destination != null && source.GetValue(row) != null)
{
Type t = Nullable.GetUnderlyingType(destination.PropertyType) ?? destination.PropertyType;
object safeValue = Convert.ChangeType(source.GetValue(row), t);
destination.SetValue(product, safeValue);
}
}
I saw something here
https://learn.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=net-6.0
it about binding flangs when reflecting model. "Specifies flags that control binding and the way in which the search for members and types is conducted by reflection." If there is way I can redirect option(1-2-3-4-5-6...) to list options
thanks for the help I solved my problem. If you need something like that, my solution is;
As you know OptionModels is what I created before, AddOptipns function is a new one I use for add data to list,
The function work with the ref, otherwise it must be static, if I turn it static, option models also must be static, so I can't access the list.
public IList<ExcelOptionViewModel> OptionModels { get; set; } = new List<ExcelOptionViewModel>();
public void AddOptions(ref String option, ref String value)
{
OptionModels.Add(new ExcelOptionViewModel { Option = option.Trim(), Value = value.Trim() });
}
And also add some new parts to convert model function,
calling that AddOptions method with reflection, I got an example from here
https://learn.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=net-6.0
I was inspired by the swap example there.
public List<T> ConvertDataTableToList<T>(DataTable dt)
{
var columnNames = dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName.ToLower()).ToList();
//selection properties equals to columnnames because I dont want loop for all props
var type = typeof(T);
var properties = type.GetProperties().Where(prp => columnNames.Any(t => t.ToLower() == prp.Name.ToLower())).ToList();
var productOptions = columnNames.Where(x => x.Contains("option")).ToList() ?? new List<string>();
return dt.AsEnumerable().Select(row =>
{
var objT = Activator.CreateInstance<T>();
foreach (var pro in properties)
{
try
{
if (row[pro.Name] != DBNull.Value)
pro.SetValue(objT, row[pro.Name], null);
else
pro.SetValue(objT, null, null);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
for (var i = 0; i < productOptions.Count(); i += 2)
{
object[] argValues = new object[] { row[productOptions[i]].ToString(), row[productOptions[i + 1]].ToString() };
String[] argNames = new String[] { "option", "value" } ;
var method = type.GetMethod("AddOptions");
method.Invoke(objT, argValues);
}
return objT;
}).ToList();
}
here is the added data :)

ComboBox keeps old values while displaying new ones

So, it's been 4 hours since I started struggling here.
See, I've got this comboBox and it's bound to a List<>, - loads up like it's supposed to; but here I've also got a textBox which is supposed to contain text for filter criteria of the List<>. Goes nice, packs all the filtered items into a new list, the comboBox displays it... But when I choose to pick an item from it, i.e. a comboBox.Item, it returns the items from the first list. Yes, the first list, displaying the values from the filtered list; those values are class objects I'm packing into a dataGridView later on.
Here's the TextChanged:
private void textBox4_TextChanged_1(object sender, EventArgs e)
{
IEnumerable<artikal> filtered =
from artikal in art
where artikal.naziv.ToUpper().Contains(textBox4.Text.ToUpper()) || artikal.plu.Contains(textBox4.Text) || artikal.barkod.Contains(textBox4.Text)
select artikal;
comboBox1.DataSource = null;
comboBox1.Items.Clear();
List<artikal> filter = filtered.ToList<artikal>();
comboBox1.DataSource = filter;
and here's the class, I mean, if it's this important, but I'm not convinced it is:
public class artikal
{
public string plu { get; set; }
public string naziv { get; set; }
public string kolicina { get; set; }
public string nabavnaCena { get; set; }
public string prodajnaCnea { get; set; }
public string barkod { get; set; }
public override string ToString()
{
return plu + " " + naziv;
}
}
This art list is a global list defined above all else in the world. Here is how I populate the gridview:
public partial class NabavkaFrm : Form
{
#region some stuff lying here
List<item> art = new List<item>();
// other code
row.Cells[0].Value = art[comboBox1.SelectedIndex].plu;
row.Cells[1].Value = art[comboBox1.SelectedIndex].naziv;
}
So, yeah, any suggestions? And good time o' the day to everyone passing by :D
As I mentioned in the comment, the issue is not in the code where you filter the items. It cannot be. The issue is right here:
row.Cells[0].Value = art[comboBox1.SelectedIndex].plu;
row.Cells[1].Value = art[comboBox1.SelectedIndex].naziv;
and because art is the class level field, if the item from the combobox is 0, it will always show item at index 0 in art. You do not want this. You want to show item at index 0 from your filtered list but that list keeps changing. Sometimes item at index 0 is one thing, whilst another thing another time.
Do it like this instead:
var selectedItem = (comboBox1.SelectedValue as item);
row.Cells[0].Value = selectedItem.plu;

Accessing Property of Class in List<Class>

I see a lot of similar questions but none with a direct answer. I have a List<ClientEntry>. I want to access properties in ClientEntry. My code looks like this:
class ClientEntry
{
private string _clientName;
private string _clientEmail;
public void ClientEntry(string name, string email)
{
this._clientName = name;
this._clientEmail = email;
}
public string ClientName
{
get
{
return _clientName;
}
set
{
_clientName = value;
}
}
public string ClientEmail
{
get
{
return _clientEmail;
}
set
{
RegexUtilities Validator = new RegexUtilities();
if (Validator.IsValidEmail(value))
{
_clientEmail = value;
}
}
}
}
Later:
private List<ClientEntry> clientList;
I then add a bunch of ClientEntry's to the List.
How can I access the ClientName and ClientEmail properties for items in clientList? Also, how can I check for the existance of a certain ClientName or ClientEmail property within the List? Is this even possible with a list of objects? I know a dict would probably serve better, but I wanted to see if this could be done with a List and a class with properties.
You can use Linq to look for values inside of a list using Any()
Eg.
bool emailExists = clientList.Any(x=>x.ClientEmail == <email>);
To access values, you can use a index accessor if you know it, loop the collection, or use Where() to search it:
var email = clientList[index].ClientEmail
or
foreach (var client in clientList)
{
var email = client.ClientEmail
}
or
var email = clientList.Where(x=>x.ClientName == <clientName>).FirstOrDefault();
you can explore your list as below
foreach (ClientEntry client in clientList)
{
//client.ClientName
//client.ClientEmail
}
to find a particular record you can search it as
clientList.Where(p=> p.ClientEmail == "email#domain.com").FirstOrDefault();
To access a specific of item in the list, you input the index / using foreach
string name = clientList[index].ClientName;
foreach(var client in clientList)
{
name = client.ClientName; // access the item one by one
}
To check the existence of certain value of a property, use linq
bool isExist = clientList.Any(i => i.ClientName == "John");
Use Extension Methods !
Something like this, you can write unit test against the extension class easily and also it's straightforward to read.
public static class ClientEntriesExtension
{
public static bool ExistEmail(this IEnumerable<ClientEntry> entries, string targetEmail)
{
return entries.Any(x=>x.ClientEmail == targetEmail);
}
}
bool exist = clientList.ExistEmail(targetEmail)

How to convert from 'System.Linq.IQueryable<System.Collections.Generic.List<Model.Record> to List<Record>

How does one retrieve the list in a model?
This is what I'm trying:
private void cbxPlayers_SelectedValueChanged(object sender, EventArgs e)
{
List<Record> records = new List<Record>();
string selectedPlayer = cbxPlayers.SelectedItem.ToString();
using (ProgressRecordContext context = new ProgressRecordContext())
{
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player.Records).ToList<Record>();
}
}
That doesn't work however, what am I missing?
These are the models in case they're needed:
public class Player
{
[Key][DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AccountNumberId { get; set; }
public string Name { get; set; }
public virtual List<Record> Records { get; set; }
}
public class Record
{
public int RecordId { get; set; }
public int AccountNumberId { get; set; }
public double Level { get; set; }
public int Economy { get; set; }
public int Fleet { get; set; }
public int Technology { get; set; }
public int Experience { get; set; }
public DateTime TimeStamp { get; set; }
public virtual Player Player { get; set; }
}
EDIT: Here's the error messages:
Error 1 'System.Linq.IQueryable>' does not contain a definition for 'ToList' and the best extension method overload 'System.Linq.ParallelEnumerable.ToList(System.Linq.ParallelQuery)' has some invalid arguments
Error 2 Instance argument: cannot convert from 'System.Linq.IQueryable>' to 'System.Linq.ParallelQuery'
EDIT:
I see that I probably wasn't very clear with what I was trying to do. I eventually worked out a way to do what I wanted and here it is:
private void cbxPlayers_SelectedValueChanged(object sender, EventArgs e)
{
lstvRecords.Items.Clear();
if(cbxPlayers.SelectedIndex == -1)
{
return;
}
string selectedPlayer = cbxPlayers.SelectedItem.ToString();
using (ProgressRecordContext context = new ProgressRecordContext())
{
var records = from Player in context.Players
from Record in context.Records
where Player.Name == selectedPlayer &&
Player.AccountNumberId == Record.AccountNumberId
select new
{
Level = Record.Level,
Economy = Record.Economy,
Fleet = Record.Fleet,
Technology = Record.Technology,
Experience = Record.Experience,
TimeStamp = Record.TimeStamp
};
foreach (var element in records)
{
string[] elements = {element.Level.ToString(),
element.Economy.ToString(),
element.Fleet.ToString(),
element.Technology.ToString(),
element.Experience.ToString(),
element.TimeStamp.ToString()
};
ListViewItem lvi = new ListViewItem(elements);
lstvRecords.Items.Add(lvi);
}
}
}
Is there a better way to write that query or is the way that I've done it correct?
No idea why you're getting ParallelQuery - unless you've got some wacky usings in your source file.
In any case, you appear to have an enumerable of enumerables - try SelectMany (note you need using System.Linq; for this to work as an extension method, too):
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player.Records).SelectMany(r => r).ToList();
Also - unless you intend to add/remove to/from that list, you should just use an array, i.e. use .ToArray().
As pointed out by #Tim S (+1) - if you expect only a single player here then you should be using SingleOrDefault() to get the single player - whose Records you then turn into an array/list.
Your problem is that Player.Records is a List<Record>, and you are getting an IEnumerable<List<Record>> (i.e. 0 to many player's records) from your query, so .ToList() gets you a List<List<Record>>. If there are multiple players with the same name and you want it to collect the records from all of them, use Andras Zoltan's solution. If you want to ensure (via throwing an exception if there are 0 or more than 1 results) that exactly one player has the given name, and only his records are returned, use one of these solutions: (key change being .Single() - also take a look at SingleOrDefault to see if it fits your needs better)
//I prefer this solution for its conciseness and clarity.
records = context.Players.Single(Player => Player.Name == selectedPlayer).Records;
//if you'd like to use the LINQ query format, I'd recommend this.
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player).Single().Records;
//this is more similar to your original query.
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player.Records).Single().ToList();
If you change
List<Record> records = new List<Record>();
to
var records = new List<List<Record>>();
Does it work? If a Player has a list of Records, it looks like your query is returning a List of a List of Records.
Edit:
There, fixed the return list... either way this is probably not the solution you're looking for, just highlighting what the problem is.
You could try refactoring your query
records = context.Players.First(player => player.Name == selectedPlayer).Records.ToList();

C# Automatically linking strings to properties using the string value

This might be a stupid one but I'll shoot it out there.
For example let's say I have a model class:
public class PermissionModel
{
public bool AppName_Home_Product_SaveButton_Enabled { get; set; }
public bool AppName_Home_Product_ConfirmButton_Enabled { get; set; }
}
And I have the following list of strings:
"AppName_Home_Product_SaveButton_Enabled_true"
"AppName_Home_Product_SaveButton_Enabled_false"
I want to automatically populate the model properties with true/false without having to use if statements as in the following example:
if (aString.Contains("AppName_Home_Product_SaveButton_Enabled"))
{
PermissionModel.AppName_Home_Product_SaveButton_Enabled = Convert.ToBoolean(AString.Substring(AString.IndexOf("Enabled_") + 8));
}
Any ideas or is this crazy? I just want to avoid a bunch of if statements to populate the model and make it more re-usable.
This can be done via reflection
const string delimiter = "_Enabled";
foreach (string data in aString) {
int index = data.IndexOf(delimiter);
if (index >= 0) {
// Get the name and value out of the data string
string name = data.Substring(0, index + delimiter.Length);
bool value = Convert.ToBoolean(data.Substring(index + delimiter.Length + 1));
// Find the property with the specified name and change the value
PropertyInfo property = GetType().GetProperty(name);
if (property != null) {
property.SetValue(this, value);
}
}
}

Categories