Delete multiple rows from data grid view - c#

I'm trying to delete multi rows from data grid view in c#
I use xml file as a database.
Here is my code; when I trying to delete in the data grid view, they are deleted correctly, but in the XML file, just the last selected row is deleted, and the sequence row after it.
var selectedRows = CustomersInformation.SelectedRows
.OfType<DataGridViewRow>()
.Where(row => !row.IsNewRow)
.ToArray();
foreach (var row in selectedRows)
{
XDocument Customersdocument = XDocument.Load(#"customers.xml");
var DeleteQuery = Customersdocument.Descendants("Customer")
.Where(del => del.Element("PhoneNumber").Value ==
CustomersInformation.CurrentRow.Cells[1].Value.ToString());
DeleteQuery.Remove();
Customersdocument.Save(#"customers.xml");
CustomersInformation.Rows.Remove(row);
CustomersInformation.ClearSelection();
}
My XML file looks like this but with more customers
<Customers>
<Customer>
<Name>sara</Name>
<PhoneNumber>7176665</PhoneNumber>
<BirthDate>12/28/2000</BirthDate>
<ExpireDate>2023-03-28T09:15:27.8040881+03:00</ExpireDate>
<PackageId>1</PackageId>
<Balance>8</Balance>
</Customer>
</Customers>

The main problem is this line:
CustomersInformation.CurrentRow.Cells[1].Value.ToString());
The keyword:
...CurrentRow...
acts differently than what you're trying to loop through all the rows in datagridview.
you're trying to do a "foreach" loop, but getting the values from "currentrow".
if you performs a
CustomersInformation.ClearSelection();
The "currentrow" will become "null".
So, u can do this, load the xml first, before the loop:
XDocument Customersdocument = XDocument.Load(#"customers.xml");
foreach (var row in selectedRows)
{
// remove row
}
// save the file after the loop finished
Customersdocument.Save(#"customers.xml");
in the loop:
foreach (var row in selectedRows)
{
// get da phoneNumber from foreach row, not currentrow
var phoneNumber = row.Cells[1].Value.ToString();
// not from currentrow
//var phoneNumber = CurrentRow.Cells[1].Value.ToString();
// generate delete query
var DeleteQuery = Customersdocument.Descendants("Customer")
.Where(del => del.Element("PhoneNumber").Value == phoneNumber);
// do remove
DeleteQuery.Remove();
// remove from teh datagridview
CustomersInformation.Rows.Remove(row);
}

According to my understanding, you should try saving CustomersDocument after the end of the for loop. If the problem is not resolved, try debugging the entire process to determine the exact error.

You can do the following :
XDocument doc = XDocument.Load("customers.xml");
var q = from node in doc.Descendants("Customer")
let attr = node.Attribute("PhoneNumber")
where //your condition here with attr.Value
select node;
q.ToList().ForEach(x => x.Remove());
doc.Save("customers.xml");

Related

C# Xpath only returning first element

I am using HTMLAgilityPack to read and load an XML file. After the file is loaded, I want to insert the values from it into a database.
XML looks like this:
<meeting>
<jobname></jobname>
<jobexperience></jobexperience>
</meeting>
I'm trying to accomplish this using XPath statements within a foreach loop as seen here:
DataTable dt = new DataTable();
//Add Data Columns here
dt.Columns.Add("JobName");
dt.Columns.Add("JobExperience");
// Create a string to read the XML tag "job"
string xPath_job = "//job";
string xPath_job_experience = "//jobexperience";
/* Use a ForEach loop to go through all 'meeting' tags and get the values
from the 'JobName' and 'JobExperience' tags */
foreach (HtmlNode planned_meeting in doc.DocumentNode.SelectNodes("//meeting"))
{
DataRow dr = dt.NewRow();
dr["JobName"] = planned_meeting.SelectSingleNode(xPath_job).InnerText;
dr["JobName"] = planned_meeting.SelectSingleNode(xPath_job_experience).InnerText;
dt.Rows.Add(dr);
}
So the problem is that even though the foreach loop is going through every 'meeting' tag, it's getting the values from only the first 'meeting' tag.
Any help would be greatly appreciated!
So the problem is that even though the foreach loop is going through every 'meeting' tag, it's getting the values from only the first 'meeting' tag.
Yes, that's what the code does. The XPath operator // selects all the elements in the whole document, e.g. //job select all job elements in the whole document.
So in your foreach loop you select all meeting elements in the whole document with
doc.DocumentNode.SelectNodes("//meeting"))
and then - in the loop - you select all //job and all //jobexperience elements in the whole document with
string xPath_job = "//job";
string xPath_job_experience = "//jobexperience";
So you select the first element of all elements - over and over again... Hence the impression that you only get the first element.
So change the code in a way that the children of the current meeting element get selected (by removing the // operator):
string xPath_job = "job";
string xPath_job_experience = "jobexperience";

Returning Value of Grouped Items when using Linq to XML

I am trying to use Linq to XML in Visual Studio - C# to pull all of the Elements in an XML file and group them by their value.
This is my XML code:
<?xml version="1.0" encoding="utf-8"?>
<topTerms>
<topTerm>Cat</topTerm>
<topTerm>Dog</topTerm>
<topTerm>Cat</topTerm>
<topTerm>Dog</topTerm>
<topTerm>Cat</topTerm>
<topTerm>Bird</topTerm>
<topTerm>Cat</topTerm>
</topTerms>
I am then using the following C# code to try and pull the data and group it by value of the topTerm element.
var top = 0;
var topName = "";
var topTermsUrl = Server.MapPath("XML/topTerms.xml");
XDocument topTermsFile = XDocument.Load(topTermsUrl);
var topTermDocuments = topTermsFile.Root
.Elements("topTerm")
.GroupBy(a => a.Value);
foreach (var topTerm in topTermDocuments)
{
topName = topTerm.Value;
top = topTerm.Count();
}
However, topTerm.Value is not working. It will count the number of occurrence for each value when I cycle through, but I cannot get the string value being counted. Any ideas?
topTerm is an IGrouping, particularly IGrouping<string, XElement>. So it has a property Key that contains the value by which you grouped:
foreach (var topTerm in topTermDocuments)
{
topName = topTerm.Key; // Key used here
top = topTerm.Count();
}
(I assume that in the full code you do more than overwriting variables in a loop).

Filling a Listview/Gridview dynamically with XML file

good afternoon guys and girls! i am trying to learn c#(WPF) for about 2 weeks now and i'm encountering some problems which google didnt help me to solve so far :/
Lets say i have a random XML file:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<ADRESSE>
<NAME1>John</NAME1>
<NAME2>Doe</NAME2>
<STRASSE1>Heystreet</STRASSE1>
<STRASSE2>9</STRASSE2>
<LAND>AUT</LAND>
<PLZ>1050</PLZ>
<ORT>Vienna</ORT>
</ADRESSE>
</XML>
Pretend this XML has like 100 entries.
Now i'll have a simple Listview called "lv1" in my XAML and a button.
int counter = 0;
GridView gv1 = new GridView();
XDocument rndfile = XDocument.Load(#"C:\Users\...\random.xml");
foreach (XElement xele in rndfile.Descendants("ADRESSE")) //#1
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding("Feld"+counter);
gvc.Header = xele.Name.LocalName; // #2
gv1.Columns.Add(gvc);
string feldx = string.Format(#"Feld{0}", counter);
// MessageBox.Show(feldx+"||"+"Feld"+counter); //was for me to test if names are identical
lv1.Items.Add(new { feldx = xele.Element("Childelement of ADRESSE").Value }); //#3+4
counter++;
}
lv1.View = gv1;
1 and 3 are my actual problems, whereby 1 and 2 are the same thing i guess.
So basically what i want to do is press the Button, load the XML and create for each Child of ADRESSE a column with the name of the current Child and directly fill it with the XML content.
The problems i am encountering now: #1 the foreach loop only runs for each entry called ADRESSE instead of each child element of it and i just cant figure out how to get the childs of it without breaking any syntax (tried it with Elements() but he doesnt like that in the loop then).. So for now the XML above would only create ONE row instead of 7, because theres only one ADRESSE entry in the file.
For the second problem i want to name the Columns with the Childname of the XML, but due to the problem at #1 it wont work as intended. (or is this thought generally wrong?)
And the third problem is the dynamical filling of the columns. As far as i could see it lv1.Items.Add({...}) does not accept the feldx from above, but thinks it is a own name and doesn't fill it correctly then, because there are no columnbindings named feldx. For #4 i need something like feldx = xele.Element(#"{0}", ChildName).Valuefor the correct content of the column then
I really tried to look and solve this on my own, but all stuff i found on XML or gridviews here or at mycsharp/msdn either was only with static (hardcoded) XAML entrys and bindings or just XML files where you know what entrys there are (so again practically hardcoded). So i just hope my request just isn't too barefaced and someone could enlighten me a little
Edit #1 :
var rootele = rndfile.Root.Element("ADRESSE").Elements(); //<-- worked like a charm. My problem was that i tried to fiddle this into the foreach loop itself, which wasn't accepted somehow - so many thanks har07
int counter = 0;
foreach (XElement xele in rootele)
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding("Feld"+counter);
gvc.Header = xele.Name.LocalName;
gv1.Columns.Add(gvc);
lv1.Items.Add(new { feld_x_ = xele.Element("Childelement of ADRESSE").Value }); // <-- for this i am trying to find a solution and post it again, or maybe someone else knows how to add a changing Binding Name into the .Add()
counter++;
}
lv1.View = gv1;
Your foreach loop is obviously tell to loop through each <ADRESSE> node, you can fix it like one of the following :
//approach 1 :
var chldNodes = rndfile.Descendants("ADRESSE").First().Elements();
//or approach 2 :
var chldNodes = rndfile.Root.Element("ADRESSE").Elements();
//then loop through childNodes
foreach (XElement xele in chldNodes)
3 & 4 looks simply wrong. That way, even if it worked, you'll ended with multiple rows, one for each column with corresponding row's column filled with value from XML and the rest columns are empty. Fix 1 & 2 then you can focused on 3 & 4, edit the question with your closest try -or open new question- if you can't make it work at the end.
EDIT :
Quick searching on google suggests that ListView with dynamic column seems not a trivial task (some related threads: 1, 2)
You may want to try this way (crafted based on link no. 2 above) :
....
var columnValues = new List<string>();
foreach (XElement xele in rootele)
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding(String.Format("[{0}]", counter));
gvc.Header = xele.Name.LocalName;
gv1.Columns.Add(gvc);
columnValues.Add((string)xele);
counter++;
}
lv1.Items.Add(columnValues);
....
Your XML file is incorrect.
<NAME2>Doe</NAME3>
should be
<NAME2>Doe</NAME2>
I'll just write the partial Solution to this now, which will require some more hardcoding than i wanted, but better than nothing - maybe someone else needs it (Many Thanks again to har07)
GridView gv1 = new GridView();
XDocument rndfile = XDocument.Load(#"C:\Users\...\rndfile.xml");
var rootele = rndfile.Descendants("ADRESSE").First().Elements();
int counter = 0;
//Create empty Listview depending on Sub-entrys of ADRESSE
foreach (XElement xele in rootele)
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding("Feld"+counter);//gets a Binding name Feld# for each child
gvc.Header = xele.Name.LocalName; //Name my Columns after the <tags> of the childs
gv1.Columns.Add(gvc); //add the current Column to my Gridview
counter++;
} //Listview created
//Fill my list for every single Parent-element in my XML file
foreach (XElement xe in rndfile.Descendants("ADRESSE"))
{
lv1.Items.Add(new
{
Feld0 = xe.Element("NAME1").Value,
Feld1 = xe.Element("NAME2").Value,
Feld2 = xe.Element("STRASSE1").Value,
Feld3 = xe.Element("STRASSE2").Value,
Feld4 = xe.Element("LAND").Value,
Feld5 = xe.Element("PLZ").Value,
Feld6 = xe.Element("ORT").Value
});
}//List filled
This way it doesn't matter in which order i have the ChildElements in the subsequent ADRESSE tags AFTER my first (Since the first Parent is needed to create the columns correctly). So the second shuffled Entry of ADRESSE below :
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<ADRESSE>
<NAME1>John</NAME1>
<NAME2>Doe</NAME2>
<STRASSE1>Heystreet</STRASSE1>
<STRASSE2>9</STRASSE2>
<LAND>AUT</LAND>
<PLZ>1050</PLZ>
<ORT>Vienna</ORT>
</ADRESSE>
<ADRESSE>
<LAND>AUT</LAND>
<PLZ>1060</PLZ>
<ORT>Vienna</ORT>
<NAME1>Jane</NAME1>
<STRASSE1>Wherestreet</STRASSE1>
<STRASSE2>11</STRASSE2>
<NAME2>Doe</NAME2>
</ADRESSE>
</XML>
Will still be filled in the correct Columns. Only Problems left now are the dependance of the Bindings (or namings of the Columns) on the First Entry and that i have to rename every X in all FeldX = xe.Element("XXX").Value if i use another kind of XML with different entries.
Still: if anyone knows a solution close to (i know that's completely broken but) something like ->
lv1.Items.Add(new{ string.Format(#"Feld{0}", counter) = xe.Element(#"{0}", xele.Name.LocalName).Value}
to put into a loop i'd be really grateful!

Getting values from deleted rows in data grids using winforms

I am using typed datasets with datagrids. When I delete a row I use the dataset.HasChanges filter and get changes as follows.
dtDel = (Database1DataSet1.product_skuDataTable)database1DataSet1.product_sku.GetChanges(DataRowState.Deleted);
I am trying to get values(Product Names) from deleted rows as follows.
private string getProdNames(DataTable dtDel)
{
string prodNames = "";
var q = dtDel.AsEnumerable().Select(x => x.Field<string>("ProductName"));
foreach (string p in q)
{
prodNames += p + "\n";
}
return prodNames;
}
But I am getting the following error.
Deleted row information cannot be accessed through the row.
Thanks
Found the answer here and here
The linq query will work like this
var q = dt.AsEnumerable().Select(x => x.Field<string>(colName, DataRowVersion.Original));

Get all <td> title of a table column with coded ui

I need to check a filter function on a table.
This filter is only on the first cell of each row and I'm trying to figure out how to get all those values...
I tried with something like
public bool CheckSearchResults(HtmlControl GridTable, string FilterTxt)
{
List<string> Elements = new List<string>();
foreach (HtmlCell cell in GridTable.GetChildren())
{
Elements.Add(cell.FilterProperties["title"]);
}
List<string> Results = Elements.FindAll(l => l.Contains(FilterTxt));
return Results.Count == Elements.Count;
}
but I get stuck at the foreach loop...
maybe there's a simply way with linq, but i don't know it so much
edit:
all the cells i need have the same custom html tag.
with this code i should get them all, but i don't know how to iterate
HtmlDocument Document = this.UIPageWindow.UIPageDocument;
HtmlControl GridTable = this.UIPageWindow.UIPageDocument.UIPageGridTable;
HtmlCell Cells = new HtmlCell(GridTable);
Cells.FilterProperties["custom_control"] = "firstCellOfRow";
also because there's no GetEnumerator function or query models for HtmlCell objects, which are part of Microsoft.VisualStudio.TestTools.UITesting.HtmlControl library -.-
edit2:
i found this article and i tried this
public bool CheckSearchResults(string FilterTxt)
{
HtmlDocument Document = this.UIPageWindow.UIPageDocument;
HtmlControl GridTable = this.UIPageWindow.UIPageDocument.UIPageGridTable;
HtmlRow rows = new HtmlRow(GridTable);
rows.SearchProperties[HtmlRow.PropertyNames.Class] = "ui-widget-content jqgrow ui-row-ltr";
HtmlControl cells = new HtmlControl(rows);
cells.SearchProperties["custom_control"] = "firstCellOfRow";
UITestControlCollection collection = cells.FindMatchingControls();
List<string> Elements = new List<string>();
foreach (UITestControl elem in collection)
{
HtmlCell cell = (HtmlCell)elem;
Elements.Add(cell.GetProperty("Title").ToString());
}
List<string> Results = Elements.FindAll(l => l.Contains(FilterTxt));
return Results.Count == Elements.Count;
}
but i get an empty collection...
Try Cell.Title or Cell.GetProperty("Title"). SearchProperties and FilterProperties are only there for searching for a UI element. They either come from the UIMap or from code if you fill them out with hand. Otherwise your code should should work.
Or you can use a LINQ query (?) like:
var FilteredElements =
from Cell in UIMap...GridTable.GetChildren()
where Cell.GetProperty("Title").ToString().Contains(FilterTxt)
select Cell;
You could also try to record a cell, add it to the UIMap, set its search or filter properties to match your filtering, then call UIMap...Cell.FindMatchingControls() and it should return all matching cells.
The problem now is that you are limiting your search for one row of the table. HtmlControl cells = new HtmlControl(rows); here the constructor parameter sets a search limit container and not the direct parent of the control. It should be the GridTable if you want to search all cells in the table. Best solution would be to use the recorder to get a cell control then modify its search and filter properties in the UIMap to match all cells you are looking for. Tho in my opinion you should stick with a hand coded filtering. Something like:
foreach(var row in GridTable.GetChildren())
{
foreach(var cell in row.GetChildren())
{
//filter cell here
}
}
Check with AccExplorer or the recorder if the hierarchy is right. You should also use debug to be sure if the loops are getting the right controls and see the properties of the cells so you will know if the filter function is right.
I resolved scraping pages html by myself
static public List<string> GetTdTitles(string htmlCode, string TdSearchPattern)
{
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(htmlCode);
HtmlNodeCollection collection = doc.DocumentNode.SelectNodes("//td[#" + TdSearchPattern + "]");
List<string> Results = new List<string>();
foreach (HtmlNode node in collection)
{
Results.Add(node.InnerText);
}
return Results;
}
I'm freakin' hating those stupid coded ui test -.-
btw, thanks for the help

Categories