Get first row of an ordered Gtk TreeView(Gtk#) - c#

I have a Gtk# TreeView with one visible column whose Model contains 3 values (text for the visible column, a value to sort the tree on and a reference to the base object). Here is the code:
internal class JpgTreeView : TreeView
{
private readonly ListStore _store = new ListStore(typeof(string), typeof(int), typeof(ITreeViewChoice));
private TreeModelSort _sortedModel;
private enum Column
{
Text,
SortValue,
Value
}
public JpgTreeView()
{
var valueColumn = new TreeViewColumn();
AppendColumn(valueColumn);
var visisbleColumnTextRenderer = new CellRendererText();
valueColumn.PackStart(visisbleColumnTextRenderer, true);
valueColumn.AddAttribute(visisbleColumnTextRenderer, "text", (int) Column.Text);
_sortedModel = new TreeModelSort(_store);
_sortedModel.SetSortColumnId((int) Column.SortValue, SortType.Descending);
_sortedModel.SetSortFunc((int) Column.SortValue, (model, a, b) =>
{
var aval = (int) model.GetValue(a, (int) Column.SortValue);
var bval = (int) model.GetValue(b, (int) Column.SortValue);
return aval.CompareTo(bval);
});
Model = _sortedModel;
}
The sorting works correctly but the _store.GetIterFirst function always retrieves the original first row (i.e. the first row of the unsorted list). _sortedModel.GetIterFirst returns the following error message:
gtk_list_store_get_value: assertion 'iter_is_valid (iter, list_store)' failed
I'm populating the TreeView using this function:
public void SetChoices(IEnumerable<ITreeViewChoice> choices)
{
_store.Clear();
foreach (var choice in choices)
{
_store.AppendValues(choice.GetChoiceText(), 1, choice);
}
}
How do I get the first row of the sorted list?

_sortedModel.GetIterFirst is the correct way to retrieve the first row of an ordered Gtk TreeView (TreeModelSort)
The issue I was having is that I was using the row returned from the TreeModelSort in the ListStore which isn't valid. i.e. this is correct:
_sortedModel.GetIterFirst(out var iter);
var value = _sortedModel.GetValue(iter, (int) Column.Value);
And this is incorrect (what i was doing):
_sortedModel.GetIterFirst(out var iter);
var value = _store.GetValue(iter, (int) Column.Value);

Related

Insert Columns and Rows into spreadsheet from IQueryable object

How do I insert the columns and rows of data from an Queryable object? What I have so far is listed below. It seems I can get the column names into the spread sheet but Im not sure how to insert the values using the method I have written.
private IQueryable<ShippingRequest> GetRecordsFromDatabase()
{
var CurrentUserId = (int)Session["UserId"];
var results = db.ShippingRequests
.Where(r => r.UserId == CurrentUserId);
return results;
}
//Create the WorkSheet
ExcelWorksheet worksheet = excelPackage.Workbook.Worksheets.Add("FedEx Rates");
//get columns of table
var columnNames = typeof(ShippingRequest).GetProperties()
.Select(x => x.Name)
.ToArray();
int i = 0;
//Adding column name to worksheet
foreach (var col in columnNames)
{
i++;
worksheet.Cells[1, i].Value = col;
}
//Adding records to worksheet
int j;
for (i = 0; i<columnNames.Length; i++)
{
foreach (var item in db)
{
worksheet.Cells[i + 2, j + 1].Value = ???; //Not sure how to get this value
}
}
So you fetched some data as a sequence, and you want every element of this sequence to be added as one row to your table. The columns are all readable public properties of ShippingRequests.
Let's create a generic solution that will add any sequence of columns and show any sequence of objects of some class.
Quite often, the names of the columns don't have to fit one-on-one to the names of all your properties. Sometimes you want to show only some properties. Sometimes you want to create different column names or show different values. Maybe you don't want to show your data to an excel sheet, but to a different kind of table?
A reusable class to define columns from some table class could be something like:
class Column<TSource>
{
public int Index {get; set;}
public string Name {get; set;}
public Func<TSource, object> PropertyValueSelector {get; set;}
public object GetValue(TSource source)
{
return this.PropertyValueSelector(source);
}
... // possible other properties, like: IsVisible, IsSortable, DisplayFormat?
}
Apparently, you want to create a sequence of columns for your ShippingRequests containing every public property of ShippingRequest. The name of the column is the identifier of the property. The index is not important.
The following function will create your sequence of Columns:
public static IEnumerable<Column<TSource>> CreateColumns<TSource>()
where TSource : class
{
return typeof(TSource).GetProperties()
.Where(property => property.CanRead) // they must be at least readable
.Select( (propertyInfo, index) => new Column<TSource>
{
Index = index,
Name = propertyInfo.Name,
PropertyValueSelector = source => propertyInfo.GetValue(source);
});
}
Once we got our data and our columns, we can fill your worksheet:
void Fill<TSource>(this ExcelWorkSheet workSheet,
IEnumerable<Column<TSource>> columns,
IEnumerable<TSource> sourceData)
{
// TODO: clear worksheet?
//Add column names to worksheet
foreach (var column in columns)
{
worksheet.Cells[1, column.Index].Value = column.Name;
}
// add the source data
int nextRowIndex = 2;
foreach (var rowData in sourceData)
{
AddRow(workSheet, nextRowIndex, columns, rowData);
++nextRowIndex;
}
}
void AddRow<TSource> AddRow<TSource>(this ExcelWorkSheet workSheet,
int rowIndex,
IEnumerable<Column<TSource>> columns,
TSource rowData)
{
foreach (var column in columns)
{
var value = column.GetValue(rowData);
worksheet.Cells[rowIndex, column.Index].Value = value;
}
}
Now that you've got this, your code will be easy:
var workSheet = ...
var columns = ...
var data = ...
worksheet.Fill(columns, data);
In your case:
var worksheet = excelPackage.Workbook.Worksheets.Add("FedEx Rates");
var columns = CreateColumns<ShippingRequest>().ToList();
var shippingRequests = GetShippingRequests();
worksheet.Fill(columns, shippingRequests);
// Bam! Done!
The nice thing is that you can use the code to fill worksheets with data from any class.
For example, I have a class Student and I want to show some columns of the 100 youngest students.
// I only want to show the following columns of students:
var studentColumns = new Column<Student>
{
new Column {Index = 1, Name = "Id", PropertyValueSelector = student => student.Id },
new Column {Index = 3, Name = "Birthday", PropertyValueSelector = student => student.Id }
new Column {Index = 2, Name = "Student Name", PropertyValueSelector = student =>
String.Format("{0} {1} {2}", student.FirstName,
student.MiddleName,
student.FamilyName} },
};
// I only want 100 youngest students:
var studentsToDisplay = GetStudents()
.OrderByDescending(student => student.BirthDay)
.Take(100)
.ToList();
// filling the worksheet is only two lines:
var worksheet = excelPackage.Workbook.Worksheets.Add("Young Students");
worksheet.Fill(studentColumns, studentsToDisplay);

How to select specific fields from dynamic list using LINQ

I am trying to get the some specific fields from dynamic object with is actually a list of any class, this class contains various fields out of those fields I want to select some specific fields using LINQ, The fields which I want to select is also passing by the user. Below is the code that I have tried using System.Linq.Dynamic.
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Linq.Dynamic;
using System.Collections;
private void Test_Click(object sender, EventArgs e)
{
List<RateInfo> lst = new List<RateInfo>();
lst.Add(new RateInfo() { id_country = "IND", id_state = 1, rate = 2.3f });
lst.Add(new RateInfo() { id_country = "IND", id_state = 2, rate = 1.1f });
lst.Add(new RateInfo() { id_country = "IND", id_state = 3, rate = 5.2f });
lst.Add(new RateInfo() { id_country = "IND", id_state = 4, rate = 6.5f });
GetDynamicData(lst, new List<string>() { "id_country", "id_state" });
}
private void GetDynamicData(dynamic list, List<string> fetchFields)
{
var data = ((IEnumerable)list).Cast<dynamic>()
.Select(r => new { r }).AsQueryable();
StringBuilder s = new StringBuilder();
//This is for test only.
//It works, the value of "id_state" and "id_state" getting appended
foreach (var item in data)
{
s.Append(item.r.id_state);
s.Append(",");
s.Append(item.r.id_country);
}
//-----------------------------------------------------
//Select the specific field data from dynamic list
StringBuilder fields = new StringBuilder();
fields.Append("new (");
foreach (var fld in fetchFields)
{
fields.Append("r." + fld);
fields.Append(",");
}
fields.Remove(fields.Length - 1, 1);
fields.Append(")");
//This does not work throws error
//"No property or field 'id_country' exists in type 'Object'"
IQueryable iq = data.Select(fields.ToString());
//For test only to check the value of selected fields
foreach (dynamic item in iq)
{
s.Append(item.id_state);
s.Append(",");
s.Append(item.id_country);
}
}
you can hughly simplify your GetDynamicData method both specifying explicit list type (GetDynamicData(IList<RateInfo> list, ...)) and leaving the list item type generic, in order to reuse the method; with this last approach in mind, you can rewrite the GetDynamicData as follows, obtaining the desired output:
private void GetDynamicData<T>(IEnumerable<T> list, List<string> fetchFields)
{
var fields = $"new ({string.Join(",", fetchFields)})";
var res = list.AsQueryable().Select(fields);
//For test only to check the value of selected fields
foreach (dynamic item in res) {
Console.WriteLine(item.id_state);
Console.WriteLine(item.id_country);
}
}
OUTPUT
1
IND
2
IND
3
IND
4
IND
EXPLANATION
I think the difference is that specifying explicitly the type (through generic T or through RateInfo) you force LINQ to know list items'type; if you use dynamic the IQueryable.ElementType of the IQuqryable instance has value System.Object, so the query fails with the error you've experienced.
You should try using generics:
private void GetDynamicData<T>(IEnumerable<T> list, List<string> fetchFields)
{
var data = list.AsQueryable();

How to dynamically GroupBy using Linq

There are several similar sounding posts, but none that do exactly what I want.
Okay, so imagine that I have the following data structure (simplified for this LinqPad example)
public class Row
{
public List<string> Columns { get; set; }
}
public List<Row> Data
=> new List<Row>
{
new Row { Columns = new List<string>{ "A","C","Field3"}},
new Row { Columns = new List<string>{ "A","D","Field3"}},
new Row { Columns = new List<string>{ "A","C","Field3"}},
new Row { Columns = new List<string>{ "B","D","Field3"}},
new Row { Columns = new List<string>{ "B","C","Field3"}},
new Row { Columns = new List<string>{ "B","D","Field3"}},
};
For the property "Data", the user will tell me which column ordinals to GroupBy; they may say "don't group by anything", or they may say "group by Column[1]" or "group by Column[0] and Column[1]".
If I want to group by a single column, I can use:
var groups = Data.GroupBy(d => d.Columns[i]);
And if I want to group by 2 columns, I can use:
var groups = Data.GroupBy(d => new { A = d.Columns[i1], B = d.Columns[i2] });
However, the number of columns is variable (zero -> many); Data could contain hundreds of columns and the user may want to GroupBy dozens of columns.
So the question is, how can I create this GroupBy at runtime (dynamically)?
Thanks
Griff
With that Row data structure what are you asking for is relatively easy.
Start by implementing a custom IEqualityComparer<IEnumerable<string>>:
public class ColumnEqualityComparer : EqualityComparer<IEnumerable<string>>
{
public static readonly ColumnEqualityComparer Instance = new ColumnEqualityComparer();
private ColumnEqualityComparer() { }
public override int GetHashCode(IEnumerable<string> obj)
{
if (obj == null) return 0;
// You can implement better hash function
int hashCode = 0;
foreach (var item in obj)
hashCode ^= item != null ? item.GetHashCode() : 0;
return hashCode;
}
public override bool Equals(IEnumerable<string> x, IEnumerable<string> y)
{
if (x == y) return true;
if (x == null || y == null) return false;
return x.SequenceEqual(y);
}
}
Now you can have a method like this:
public IEnumerable<IGrouping<IEnumerable<string>, Row>> GroupData(IEnumerable<int> columnIndexes = null)
{
if (columnIndexes == null) columnIndexes = Enumerable.Empty<int>();
return Data.GroupBy(r => columnIndexes.Select(c => r.Columns[c]), ColumnEqualityComparer.Instance);
}
Note the grouping Key type is IEnumerable<string> and contains the selected row values specified by the columnIndexes parameter, that's why we needed a custom equality comparer (otherwise they will be compared by reference, which doesn't produce the required behavior).
For instance, to group by columns 0 and 2 you could use something like this:
var result = GroupData(new [] { 0, 2 });
Passing null or empty columnIndexes will effectively produce single group, i.e. no grouping.
you can use a Recursive function for create dynamic lambdaExpression. but you must define columns HardCode in the function.

select value from array at desired index using LINQ

i have an array which have 5 values from index 0 to 4. i want to store the values in my 5 model properties.
public IEnumerable<fields> ConvertTList(List<string[]> rows)
{
var tList = new List<fields>();
foreach (var item in rows)
{
var ListReading = new fields
{
//model properties names are:
// date, user,campaign,adgroup,changes
};
tList.Add(ListReading);
}
return (tList);
}
this is my code when foreach is executed item get 5 values. i want to store the values in model. how i can i store them using linq
Maybe you mean something like this:
public IEnumerable<fields> ConvertTList(List<string[]> rows)
{
return rows.Select(x => StringsToField(x));
}
fields StringsToField(string[] source)
{
return new fields
{
date = source[0],
user = source[1],
campaign = source[2],
adgroup = source[3],
changes = source[4],
};
}

Iterating and creating new anonymous types dynamically

I have an anonymous type of this form:
new List<MyList>()
{
new Column { Name = "blah", Width = 100, Hidden = true },
new Column { Name = "blah1", Width = 60, Hidden = false }
}
How can I go about creating the content within the list dynamically, like:
new List<MyList>()
{
foreach (var columns in col)
{
new Column { Name = columns.Name ... }
}
}
Even with col returning the right sort of data, the above example isn't acceptable and I can't see why.
You try to loop over the collection inside the object initializer block (thx to Luke).
Try creating the list first and than filling it,
var list = new List<MyList>();
foreach (var columns in col)
{
list.Add(new Column { Name = columns.Name ... });
}
Not sure exactly what you're asking, but have you tried something of the form:
col.Select(c => new Column {Name = c.Name ... etc}).ToList();
maybe something like
var theList = new List<MyList>();
col.ForEach(c=> theList.Add( new Column(){ Name=c.Name ... etc } ));

Categories