In WPF, I can group, but the default is group with ascending. One of my need to to be able to control the sorting of the group (Ascending or descending). For example:
group 1
item 1.1
item 1.2
item 1.3
group 2
item 2.1
item 2.2
and also be able to switch to:
group 2
item 2.1
item 2.2
group 1
item 1.1
item 1.2
item 1.3
//Here is the function to setup a group for a particular column:
private void SetupGrouping(DataGrid parentGrid, DataGridColumn col)
{
if (parentGrid == null || col == null || string.IsNullOrEmpty(col.SortMemberPath))
return;
ICollectionView vw = GetDefaultView();
if (vw != null && vw.CanGroup)
{
if (vw.GroupDescriptions.Count != 0)
{
vw.GroupDescriptions.Clear();
}
PropertyGroupDescription gd = new PropertyGroupDescription(col.SortMemberPath);
// Check to see if the column is Priority, if it is
// then do the grouping with high priority (3) on top.
// The order should be High(3), Normal (2), Low(1)
DataGridColumn priCol = GetColumnByID(ColumnFlags.Priority);
if(col == priCol)
{
// Attempted to change the direction of the sort added by adding group.
// However, it has error complaining SortDescription is sealed
// and can't be changed.
//if (vw.SortDescriptions != null && vw.SortDescriptions.Count > 0)
//{
// SortDescription sd = vw.SortDescriptions[0];
// if (sd.PropertyName == col.SortMemberPath)
// {
// sd.Direction = ListSortDirection.Descending;
// }
//}
}
// Info: when we add a new GroupDescription to GroupDescriptions list,
// guest what? a new SortDescription is also added to the
// SortDescriptions list.
vw.GroupDescriptions.Add(gd);
}
// Save off the column for later use
GroupedColumn = col;
// Set the DataGrid's Tag so that the GroupSyle can get the column name
parentGrid.Tag = DispatchAttachedProperties.GetColumnHeader(col);
}
You had the right idea. Add a SortDescription to the ICollectionView based on the same property you are grouping on. If you want to change sort directions, you have to clear the existing one and add a new for the opposite direction. You can't change it once its created as you discovered.
Related
I have a DataGridView in a Winform desktop project in net 6.0
This DataGridView's generic row is composed by a DataGridViewCheckBoxCell in the first column, and another 6 DataGridViewTextBoxCells in the subsequent columns : the first one of the DataGridViewTextBOxCell (column 1) is permanently ReadOnly.
What I want to do is make the Cells from the second TextBox to the last one (that is columns 2 to 6) ReadOnly when the CheckBox is unchecked, and restore them to ReadWrite when the checkbox is checked.
What I do is override the dataGridViev.CellClick event, and when the sender is the element in column 0 (the checkbox) I get the row index and I iterate on the cells of the row from index 2 to 6 and set the ReadOnly property to true or false depending on the state of the checkobx itself.
The mechanism basically works fine, I read the event on the exact column/row, I correctly get the state of the checkobox (maybe in a not-so-smart way...) : but when it comes to setting the ReadOnly property I can see that ,in spite of iterating on the 2 to 6 column, it skips the cell in column 2 and sets the one in column 1 instead: all the settings on the other columns work correctly. I thought it was a matter of configuring the columns in the designer but all the columns from 2 to 6 are set identically.
My event handler is the following :
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0) // if checkbox
{
int row = e.RowIndex;
bool setState = true;
DataGridViewCheckBoxCell check = (DataGridViewCheckBoxCell)(dataGridView1.Rows[row].Cells[0]);
if (check.Value != null && check.Value == "true")
{
setState = true;
check.Value = "false";// CheckState.Unchecked;
}
else
{
setState = false;
check.Value = "true";// CheckState.Checked;
}
for (int i = 2; i < dataGridView1.Rows[row].Cells.Count; i++)
{
DataGridViewTextBoxCell txt = (DataGridViewTextBoxCell)(dataGridView1.Rows[row].Cells[i]);
txt.ReadOnly = setState;
if (setState) txt.Style.BackColor = Color.Gray;
else txt.Style.BackColor = Color.White;
}
}
}
I have requirement to sort group summary field.
Ex. I have 3 columns in the grid.
Step 1 : I have group by Id by dragging the Id column in Group by area.
Step 2: Add Sum,Count,Average on column.
Now i want to sort sum or count or average by clicking on that ,so that the whole grouped is sorted by sum like 100,200,300.
please help
The sort order is controlled by the GroupByComparer of the FieldSettings class and this can be accomplished by creating a custom IComparer for the field that is grouped. Note that grouping is actually also a sort so I am going to assume that you still want the default sort to happen when the column is first grouped.
In the following example group by records can be sorted by a single summary result when it is clicked on. This was accomplished by using a custom IComparer for the groups that sorts by the value of the tag if it is set and if not set falls back to the value of the group by record:
public class SummarySortComparer : IComparer
{
public int Compare(object x, object y)
{
GroupByRecord xRecord = x as GroupByRecord;
GroupByRecord yRecord = y as GroupByRecord;
IComparable xValue = xRecord.Value as IComparable;
object yValue = yRecord.Value;
if (xRecord.Tag != null)
{
xValue = xRecord.Tag as IComparable;
yValue = yRecord.Tag;
}
return xValue.CompareTo(yValue);
}
}
This is set on the grid using the following:
this.XamDataGrid1.FieldSettings.GroupByComparer = new SummarySortComparer();
Use the PreviewMouseLeftButtonDown of the grid to get the summary that was clicked on if there was one and set the tag of the group by records to be the value of that summary and refresh the sort of the grid:
void XamDataGrid1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SummaryResultPresenter summaryResultPresenter =
Utilities.GetAncestorFromType(e.OriginalSource as DependencyObject, typeof (SummaryResultPresenter), false) as
SummaryResultPresenter;
if (summaryResultPresenter != null)
{
GroupBySummariesPresenter groupBySummariesPresenter =
Utilities.GetAncestorFromType(summaryResultPresenter,
typeof(GroupBySummariesPresenter), false) as GroupBySummariesPresenter;
if (groupBySummariesPresenter != null)
{
SummaryResult summaryResult = summaryResultPresenter.SummaryResult;
int summaryResultIndex = summaryResult.ParentCollection.IndexOf(summaryResult);
foreach (GroupByRecord groupRecord in groupBySummariesPresenter.GroupByRecord.ParentCollection)
{
groupRecord.Tag = groupRecord.ChildRecords.SummaryResults[summaryResultIndex].Value;
}
this.XamDataGrid1.Records.RefreshSort();
}
}
}
Note that there are a few limitations in this example in that I haven't implemented any way to clear what summary is sorted so that is something that if desired would still need to be implemented by you. I also didn't include logic to change the sort direction and used the direction that the field is currently sorted by so if you also want to update the direction this will need to be added as well.
Im trying to disable a RadGridView row based on a value in that row?
I have tried the below code in the RowLoaded event but it seems to disable all rows.
var isEnrolled = (from c in masterList
where c.StudentId == StudentID
where c.Rolls[0].RollId == item.RollId.ToString()
select c.IsEnrolled).FirstOrDefault();
if (isEnrolled)
e.Row.IsEnabled = false;
Can it be done in same event, so if a row contains the word "Confirmed" the row is disabled?
foreach(DataGridRow item in RollListGrid.Items)
{
//Disable row if it contains "Confirmed"
}
And one more, how can i check a checkbox in a row if the query returns true?
var isProspectiveStudent = (from c in masterList
where c.StudentId == StudentID
where c.Rolls[0].RollId == item.RollId.ToString()
select c.IsProspectiveStudent).FirstOrDefault();
if (isProspectiveStudent){
//Check the relevant checkbox
}
Thanks!!
I managed to achieve what I wanted to with the below code.
foreach (var x in e.Row.Cells)
{
if (((GridViewCell)x).Value != null && ((GridViewCell)x).Value.ToString() == "Confirmed" && x.Column.UniqueName.IndexOf("5") != -1)
{
e.Row.IsEnabled = false;
break;
}
}
I bound a DataTable to a datagrid in wpf. The first column has unique values. Now I would like to autoselect a cell in the second column whose first column cell has the given value. How should I achieve that? For example,here is my datagrid:
Name | Age
cat | 2
dog | 3
When user input 'dog', I will need the '3' to be selected.
I tried the method show here:
How to select a row or a cell in WPF DataGrid programmatically?
However, I cannot figure out the displaying row number. Even though I know the row number of the dataTable, the display number can be different since I allow users to sort the table.Thanks a lot.
Set your grid's SelectionUnit property to "Cell", and assuming you feed the DataGrid with the table's DefaultView:
private void button1_Click(object sender, RoutedEventArgs e)
{
// Search for the source-row.
var Element = MyDataTable.AsEnumerable()
.FirstOrDefault(x => x.Field<string>("Name") == "horse");
if (Element == null) return;
// Found the row number in the DataGrid
var RowOnGrid = MyGrid.Items.OfType<DataRowView>()
.Select((a, Index) => new { data=a.Row, index = Index })
.Where(x=> x.data == Element)
.Select(x => x.index)
.FirstOrDefault();
// Assuming the desired column is the second one.
MyGrid.SelectedCells.Clear();
MyGrid.SelectedCells.Add(new DataGridCellInfo(MyGrid.Items[RowOnGrid], MyGrid.Columns[1]));
}
It should work even if you re-sort the rows.
This article shows how to implement a copy operation on a drop event. I'd like to do the same but I want my dropped item to appear in the collection according to where it was placed on the UI. So I need the StartIndex much like on a NotifyCollectionChangedEventArgs when an ObservableCollection changes. In the article you'll see that eventually you get a SelectionCollection object whose items have an Index property. But unfortunately this is the index of the source collection (where it was picked) and not the destination collection (where it was dropped).
Ok, this is quite ugly, but I didn't find another way, not by myself and also not by searching the net for answers. Must have been another deadline at Microsoft that prevented the rather obvious functionality to be included...
Basically the method below does everything manually, getting the drop location and checking it for listbox items to use as index references.
private void ListBoxDragDropTarget_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
// only valid for copying
if (e.Effects.HasFlag(DragDropEffects.Copy))
{
SelectionCollection selections = ((ItemDragEventArgs)e.Data.GetData("System.Windows.Controls.ItemDragEventArgs")).Data as SelectionCollection;
int? index = null;
if (selections != null)
{
Point p1 = e.GetPosition(this.LayoutRoot); // get drop position relative to layout root
var elements = VisualTreeHelper.FindElementsInHostCoordinates(p1, this.LayoutRoot); // get ui elements at drop location
foreach (var dataItem in this.lbxConfiguration.Items) // iteration over data items
{
// get listbox item from data item
ListBoxItem lbxItem = this.lbxConfiguration.ItemContainerGenerator.ContainerFromItem(dataItem) as ListBoxItem;
// find listbox item that contains drop location
if (elements.Contains(lbxItem))
{
Point p2 = e.GetPosition(lbxItem); // get drop position relative to listbox item
index = this.lbxConfiguration.Items.IndexOf(dataItem); // new item will be inserted immediately before listbox item
if (p2.Y > lbxItem.ActualHeight / 2)
index += 1; // new item will be inserted after listbox item (drop location was in bottom half of listbox item)
break;
}
}
if (index != null)
{
foreach (var selection in selections)
{
// adding a new item to the listbox - adjust this to your model
(lbxConfiguration.ItemsSource as IList<ViewItem>).Insert((int)index, (selection.Item as ViewItem).Clone());
}
}
}
}
}