I have two DataTables. I simply wanna save one of them in the other and then change the column name on my "temporary table".
My problem is that everytime that I change the column name from one table, it is automatically changing in the other.
declaration:
public DataTable DataTablefromDataBaseFormDataType { get; private set; }
How do I get value for the first DataTable:
DataTablefromDataBaseFormDataType = ((FormProject)Fproject).DataTablefromDataBaseFormProject;
How do I copy and change column name:
DataTableUsedToPlotGraphTemp = DataTablefromDataBaseFormDataType;
DataTableUsedToPlotGraphTemp.Columns[column_to_use].ColumnName = "temp";
Rest of the code (if needed)
DataTableUsedToPlotGraphTemp = DataTablefromDataBaseFormDataType;
DataTableUsedToPlotGraphTemp.Columns[column_to_use].ColumnName = "temp";
// This point! - When I change DataTableUsedToPlotGraphTemp it is also changing //DataTableUsedToPlotGraphTemp
if (ReceiveStartDate == null && ReceiveEndDate == null)
{
ReceiveStartDate = DataTableUsedToPlotGraphTemp.Rows[0][4].ToString();
ReceiveEndDate = DataTableUsedToPlotGraphTemp.Rows[DataTableUsedToPlotGraphTemp.Rows.Count-1][4].ToString();
}
if (ReceiveNameFile != "All files")
{
string query = $"TIME_FORMATTED >='{ReceiveStartDate}' AND TIME_FORMATTED <='{ReceiveEndDate}'";
DataRow[] result = DataTableUsedToPlotGraphTemp.Select(query);
foreach (DataRow row in result)
{
//MessageBox.Show(row[0].ToString());
DataTableUsedToPlotGraph.ImportRow(row);
}
}
else
{
DataRow[] result = DataTablefromDataBaseFormDataType.Select("FILE_NAMES = '"+ ReceiveNameFile + "'");
foreach (DataRow row in result)
{
//MessageBox.Show(row[0].ToString());
DataTableUsedToPlotGraph.ImportRow(row);
}
}
}
I already debugged several times to see if it is happening in some other place of the code. The moment that it changes the column name, it changes for both DataTables.
Can anybody help me?
Thank you.
You don't have two different DataTables(reference type) but only one:
DataTableUsedToPlotGraphTemp = DataTablefromDataBaseFormDataType;
So if you change one you are changing both. Those are just variables that reference the same object. You can use DataTable.Copy:
DataTable DataTableUsedToPlotGraphTemp = DataTablefromDataBaseFormDataType.Copy();
If you just need an empty table with the same columns you can use DataTable.Clone.
Maybe this explains it a little bit better:
For a value type, the value is the information itself. For a reference
type, the value is a reference which may be null or may be a way of
navigating to an object containing the information.
For example, think of a variable as like a piece of paper. It could
have the value "5" or "false" written on it, but it couldn't have my
house... it would have to have directions to my house. Those
directions are the equivalent of a reference. In particular, two
people could have different pieces of paper containing the same
directions to my house - and if one person followed those directions
and painted my house red, then the second person would see that change
too. If they both just had separate pictures of my house on the paper,
then one person colouring their paper wouldn't change the other
person's paper at all.
Related
I have to fill an excel like this
Where I have merged cells on each row.
I'm trying to use loadfromcollection, but it ignores the merged cells and it fills each single row.
Please did you know any quick way to do it?
edit:
I think that one way is to implement a custom loadfromcollection where the steps are:
Iterate collection
For each object, read properties/fields using reflection
Iterate cell on each row
check if range address is in worksheet.mergecells
fill it with property/field value
But in case of complex worksheet (with some loadfromcollection), I think that this approach could be heavy with a big cost.
edit 2:
Add an example:
Code like this
public class MyObj
{
public string first {get; set;}
public string second {get; set;}
}
...
List<MyObj> myList = new List<MyObj>() {
new MyObj() {first = "first1", second = "second1"},
new MyObj() {first = "first2", second = "second2"} };
...
ws.Cells["A1"].LoadFromCollection(myList);
In a normal worksheet, loadfromcollection has the following output:
If I merge column A and B after filled, I get this:
In case that my worksheet template is
and then I try to fill it using loadfromcollection I get this result
loadfromcollection ignores merged cell, and fill column A and B, but my expectation is
The problem is that this is an ´intended´ behavior in excel. When you have values in Columns A and B and you merge them, only the value in column A will remain. LoadFromCollection does not have an overload for this kind of scenario.
I can think of several ´workarounds´ which could work, depending on how general you want the code to be.
Your solution of iterating through object properties with reflection would work. If the ´merging´ is always two columns, I´d recommend using a column counter and add 2 to it each property iteration.
So something like this (where of course you already defined the excel package and worksheet etc and the start row and column):
var type = testObject.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
cell = worksheet.Cells[row, column];
cell.Value = property.GetValue(testObject);
column += 2;
}
Alternatively, you could decide to remove the merging from the template and move it to your code instead. This may provide a little bit more consistency and flexibility as well.
Code above would then look more like:
var type = testObject.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
cellRange = worksheet.Cells[row, column, row, column + 1];
cellRange.Merge = true;
cellRange.Value = property.GetValue(testObject);
column += 2;
}
You could even go a bit 'crazy' and make the range variable based on the property. You make a switch statement with all known properties which should be 2 merged cells, those which should be 3 merged cells etc. Then make the default 1 cell/column. This way you gain total control over the output and it's much easier to adjust in the future.
Edit
My case is a little bit more complex, not all my mergedcells are composed by 2 cells
Some code to illustrate my above statement with some code to help address this. Used the old switch notation as you said it was an 'old project' and I assume you aren't using C# 8 or higher yet.
var type = testObject.GetType();
var properties = type.GetProperties();
int columnMergeSize;
foreach (var property in properties)
{
columnMergeSize = GetPropertyColumnMergeSize(property.Name);
cellRange = worksheet.Cells[row, column, row, column + columnMergeSize];
cellRange.Merge = true;
cellRange.Value = property.GetValue(testObject);
column += 2;
}
private int GetPropertyColumnMergeSize(string propertyName)
{
switch (propertyName)
{
case "Property1":
case "Property2":
case "Property3":
case "Property4":
return 2;
case "Property5":
case "Property6":
return 3;
default:
return 1;
}
}
This will be less heavy than having to read out the properties of the cell each time to see if it's 'merged' or not. I've also found that if you remove nearly all formatting from an excel template it will load much faster speeding up your programme.
You can either decide to put this in a service and just inject the method into wherever it's needed with DI, or you can make it a static method and use it.
foreach(Row in table.Search("*","",rowinstance.recycle()))
{
City current_city = new City();
current_city.cityname = row.GetString("cityname");
PointShapeBuffer geometry = row.GetGeometry();
psri.FileGdb.point point = geometry.point;
current_city.xcord = point.x;
current_city.ycord = point.y;
current_city.popCateogry = row.getInteger("pop");
}
In above code i am retrieving different values from one row like row.GetString(), row.GetInteger() etc, How about if I don't know the data type of that row, how can I retrieve values from row generically?
I have tried var type = row.GetType() but its not working, its giving some other sort of information, not about the datatype.
A row in a geodatabase feature class or table will have multiple associated data types. Using your example code, each row will have a geometry attribute, a population attribute (integer), a city name attribute (string), and so on.
You should instead by finding the data type of each attribute (field) before you try to access data from it, and then have appropriate conditionals to handle that.
IField.Type Property (Esri reference page) may get you started.
We've got problem with filtering for some of our columns in devexpress gridcontrol. We add the column dynamically (bound-type column) to the grid. The values from the source objects are long type. In the cells it seems that values are fine (since they're aligned to the right without any custom formating on our side) however in filter popup values behave like strings.
For example data set like 1,2,5,11,22,37 the filter list is sorted like 1,11,2,22,5,37 (just like strings) and when we choose one of the available values the filtering does not work (i mean, grid becames empty). Even filters like "Non empty cells" does not work, but when we choose "empty cells" only few from few thousand rows are shown even if most of the cells have no values.
It is important to point out that only dynamically added columns behave that way, the few columns we create every time our module runs work as intended.
The data source is a container (List like).
We're using DevExpress 13.2.
Example of creating 'custom column'
void CreateColumn(GridColumn gridColumn, string fieldName = null, string caption = null, bool visible = true,
bool readOnly = true, UnboundColumnType unboundType = UnboundColumnType.Bound,
int columnWidth = int.MinValue, int minColumnWidth = int.MinValue)
{
gridColumn.Caption = caption;
if (fieldName != null)
gridColumn.FieldName = fieldName;
gridColumn.Visible = visible;
gridColumn.OptionsColumn.ReadOnly = readOnly;
gridColumn.OptionsColumn.AllowEdit = !readOnly;
gridColumn.UnboundType = unboundType;
gridColumn.OptionsFilter.AllowAutoFilter = true;
gridColumn.FilterMode = ColumnFilterMode.Value;
gridColumn.OptionsFilter.AutoFilterCondition = DevExpress.XtraGrid.Columns.AutoFilterCondition.Contains;
if (columnWidth != int.MinValue)
{
gridColumn.Width = columnWidth;
gridColumn.OptionsColumn.FixedWidth = true;
}
if (minColumnWidth != int.MinValue)
gridColumn.MinWidth = minColumnWidth;
}
GridColumn gridColumn = new GridColumn();
CreateColumn(gridColumn, "someName", "someCaption", true, true);
View.Columns.Add(newGridColumn);
That's how it goes in our code (striped most of not related code just to give example process).
#edit
There's invalid cast exception when we add filter like this:
ColumnFilterInfo filter = GetFilter(); //cant really post code of this
ourGrid.MainView.ActiveFilter.Add(column, filter); // VS points here
Unfortunately it doesnt say what and where (except some 'lambda expression') exception is being thrown.
Of course column is the column mentioned above.
#edit
I've found new "tip". The FilterItem objects contain strings for sure, however they should contain long values. How can we influence the creation of these or atleast where to check why those are created like that (we dont do it manually)?
#Edit 19.11.2015
Alright, I had some breakthrough. The columns ('custom') thanks to our mechanism guess their type just fine. Then only problem is that infact our values which custom columns use are stored in Dictionary<string,object>-like collection and we think that thanks to PropertyDescriptor type for columns is fine, but for some reason FilterItem objects have Value of string. We belive that's because DX filter mechanism can't really guess type of "object" so it uses simple ToString on it so FilterItem.Value is string type instead column's data type.
How to overcome this?
We've found the solution and the mistake was on our side. Column creation etc is fine. Somewhere deep, someone has changed value types.
I am able to apply NumberFormat on pivot fields using Microsoft.Office.Interop.Excel.PivotFields property. But that will not apply same format to summed fields. Is there any separate property for summed fields in pivot table that I am missing ?
Microsoft.Office.Interop.Excel.PivotFields pFields = pivotTable.PivotFields();
foreach (Microsoft.Office.Interop.Excel.PivotField pf in pFields)
if (pf.DataType == XlPivotFieldDataType.xlNumber)
pf.NumberFormat = "#,##0.00";
The documentation on these members isn't exactly crystal clear, but after a bit of digging around and trial and error it looks like the property you want is PivotTable.VisibleFields, which you need to cast to a PivotFields object to iterate through:
Excel.PivotFields vFields = (Excel.PivotFields)pivotTable.VisibleFields;
This will give you all visible fields in the table (as opposed to PivotTable.PivotFields() which only seems to give you the row labels column and the underlying source data). You might have to check the value of each one before you set the number format otherwise I believe it will apply it to everything in the table, which might not be what you want:
foreach (Excel.PivotField pf in vFields)
{
if (pf.DataType == Excel.XlPivotFieldDataType.xlNumber
& pf.Value == "Sum of Price")
{
pf.NumberFormat = "#,##0.00";
}
}
If you wanted to get to one cell in particular, then you can access the PivotField.PivotItems and iterate through those for each PivotField object:
Excel.PivotItems pi = (Excel.PivotItems)pf.PivotItems();
But I think most of the properties of PivotItem are read-only.
EDIT: Solved this myself - obviously won't work as sorting the dataTable doesn't sort the underlying data - created a dataView from the table, works fine.
I have a datatable which I am sorting and then iterating through to remove duplicate values in one column, however the output is not as expected.
Datatable structure:
infoRow["Title"]
infoRow["QuickLink"]
infoRow["Description"]
infoRow["Date"]
infoRow["MonthName"]
I'm sorting like this, which works fine, and produces a table ordered in ascending month order:
dataTable = dataTable.DefaultView.ToTable(true);
dataTable.DefaultView.Sort = "Date asc";
After the sort, I'm using the code below to compare each row to the previous, and if the MonthName value is the same, replaced it with an empty string:
string prevMonthName = "";
foreach (DataRow row in dtEvents.Rows)
{
string strMonthName = row["MonthName"].ToString();
if (strMonthName == prevMonthName)
{
row["MonthName"] = "";
row.AcceptChanges();
}
prevMonthName = strMonthName;
}
So, the problem I'm having is that even when I run the MonthName loop after the sort, it appears to be running against the unsorted data. It's like DefaultView.Sort only affects the rendered output without physically reordering the table, hence the second part of the code doesn't produce the result I need. Should I maybe be using DataView or am I just way off track...
I was actually having a similar, but slightly different problem and your question gave me an idea. As it turns out, your code was incredibly close to what you (and I) need. All you need to do is flip those two lines of sorting code like so:
dataTable.DefaultView.Sort = "Date ASC";
dataTable = dataTable.DefaultView.ToTable(true);
Now, the first line of code sorts the DefaultView. This would be enough for your DataGridView or ComboBox or whatever you're using for display, because they make use of the DefaultView. However, the DataTable, itself, remains unsorted. Therefore, the second line sets the DataTable to look exactly like the sorted DefaultView.
I just noticed your edit at the top which says you've solved it. That 'solution' seems to be more of a workaround. Seeing as how you had the right code but in the wrong order, I figured you would be interested in this answer.
Assuming that dtEvents is referencing the same object as datatable, you could try this:
string prevMonthName = "";
foreach (DataRowView row in dtEvents.DefaultView)
{
string strMonthName = row["MonthName"].ToString();
if (strMonthName == prevMonthName)
{
row["MonthName"] = "";
row.AcceptChanges();
}
prevMonthName = strMonthName;
}
Just for fun I figured out how to do this using Linq to SQL (assuming I had a sql table with your above schema). Since I spent the time figuring it out, I thought I might as well share it.
// Order the table and add an index column
var ordered = MonthTests.OrderBy(mt => mt.Date)
.AsEnumerable()
.Select((mt, index) => new
{
OrderId = index,
Record = mt
});
// Select out what we want
var query = from item in ordered
let prev = ordered.FirstOrDefault (q => q.OrderId == (item.OrderId-1))
select new
{
Title = item.Record.Title,
QuickLink = item.Record.QuickLink,
Date = item.Record.Date,
MonthName = (prev != null && prev.Record.MonthName == item.Record.MonthName) ? "" : item.Record.MonthName
};
Have fun.