Collapse all groups except the first one - wpf

I have a DataGrid with grouped ItemsSource. There are an expander on each group, so I can expand/collapse all the groups. Now, I'm trying to collapse all groups by default, but leave the first group expanded. The items source is dynamic, so I can't build any converter to check the group name. I must do it by group index.
Is it possible to do in in XAML? Or in code-behind?

This might be a little late, but in order to help with similar problems, defining a "Visual tree helper class" would be helpful in this case.
// the visual tree helper class
public static class VisualTreeHelper
{
public static Collection<T> GetVisualChildren<T>(DependencyObject current) where T : DependencyObject
{
if (current == null)
return null;
var children = new Collection<T>();
GetVisualChildren(current, children);
return children;
}
private static void GetVisualChildren<T>(DependencyObject current, Collection<T> children) where T : DependencyObject
{
if (current != null)
{
if (current.GetType() == typeof(T))
children.Add((T)current);
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(current); i++)
{
GetVisualChildren(System.Windows.Media.VisualTreeHelper.GetChild(current, i), children);
}
}
}
}
// then you can use the above class like this:
Collection<Expander> collection = VisualTreeHelper.GetVisualChildren<Expander>(dataGrid1);
foreach (Expander expander in collection)
expander.IsExpanded = false;
collection[0].IsExpanded = true;
the credit goes to this forum

I was able to solve this in my ViewModel.
The Expander is defined in the template of the DataGrids GroupStyle. The Binding must be TwoWay but triggered explicitly, so clicking in the View does not update the ViewModel. Thanks Rachel.
<Expander IsExpanded="{Binding DataContext.AreAllGroupsExpanded, RelativeSource={RelativeSource AncestorType={x:Type local:MyControl}}, UpdateSourceTrigger=Explicit}">
...
</Expander>
Then I can just set the property AreAllGroupsExpanded in my ViewModel.

I don't believe it can be done in the XAML, but it can be done in code-behind. Here is one solution that I tested in Silverlight. It should probably work just as well in WPF.
// If you don't have a direct reference to the grid's ItemsSource,
// then cast the grid's ItemSource to the type of the source.
// In this example, I used a PagedCollectionView for the source.
PagedCollectionView pcv = (PagedCollectionView)myDataGrid.ItemsSource;
// Using the PagedCollectionView, I can get a reference to the first group.
CollectionViewGroup firstGroup = (CollectionViewGroup)pcv.Groups[0];
// First collapse all groups (if they aren't already collapsed).
foreach (CollectionViewGroup group in pcv.Groups)
{
myDataGrid.ScrollIntoView(group, null); // This line is a workaround for a problem with collapsing groups when they aren't visible.
myDataGrid.CollapseRowGroup(group, true);
}
// Now expand only the first group.
// If using multiple levels of grouping, setting 2nd parameter to "true" will expand all subgroups under the first group.
myDataGrid.ExpandRowGroup(firstGroup, false);
// Scroll to the top, ready for the user to see!
myDataGrid.ScrollIntoView(firstGroup, null);

Related

WPF MVVM Listview itemsource binding with list in a class property

I have a page with two parts :
A Listview with itemsource binded with a class, ObservableCollection "Patients" loaded with "Patient" class.
Under the Listview are Textboxes binded with the selecteditem Patient. Everything works without writing any code in the page, except in selection_changed to scroll to selected item.
A second Listview must display the details "Visites" from the selected "Patient".
The application works in a MVVM Framework with a Viewmodel containing the properties for the page.
The problem is to make the relation between the two ListView. I tried first building the second list "Visites" in the NotifyPropertyChanged event :
if (Patient.ID > 0)
{
LoadVisite(Patient.ID); // fill the details list "Visites"
NotifyPropertyChanged("Visites");
}
No delail is shown when "Patient" is selected.
I tried another solution inserting the list of details in the master class "Patient like this :
public Class Patient
...
public ObservableCollection<ClsVisite> Visites
{
get
{
return _visites;
}
set
{
_visites = value;
}
}
// WDABase class to open the database and load data connection
WDABase wd = new WDABase();
wd.LoadListeVisites(ID, _visites); //ID is the relation beween the two tables
}
}
Now I try to create the Listview detail itemsource in the XAML like this :
<ListView Name="ListeVisites" ItemsSource="{Binding Path=Patient.Visites}" SelectedItem="{Binding Visite}">
No details where shown.
The unique solution I found was to add some code in the selection_changed event of the master Listview like this (in this case the listviews are in two different frame) :
private void ListePatients_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ListePatients.ScrollIntoView(ListePatients.SelectedItem);
if(ListePatients.SelectedItem != null)
{
var w1 = ((System.Windows.FrameworkElement)App.Current.MainWindow.Content).Parent;
Frame pageVisite = (w1 as MainWindow).Visit;
var w2 = (System.Windows.FrameworkElement)pageVisite.Content;
ListView Lv = (w2 as Visite).ListeVisites;
Lv.ItemsSource = (ListePatients.SelectedItem as ClsPatient).Visites;
}
}
And it's works but is there another elegant solution to bind the itemsource of the details Listview ?
Thanks for helping me.
Jean-Marie
I found the solution to my problem.
The Reason the details were not displayed is because the frame used for the listview is not common with the master frame.
I discover that when I display the details frame the "Patient" object contains only null. I Don't know why but I read about sharing ViewModel and now I use a common one in App.xaml and I Don't need to specify the itemsource for the details Listview in code. I put in XAML itemsource={Binding Patient.Visites} and everything works.

DataGridColumn Binding in code

Does anyone know how I can do the equivalent XAML binding in code?
<DataGrid ... >
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding Description}" <=== set in code **
/>
</DataGrid.Columns>
</DataGrid>
Cheers,
Berryl
=== UPDATE ====
It looks like the method I have been looking for is DataGridColumn.GenerateElement
If so, then the focus of this question is now how to set the Binding correctly. The reason I want to do this code is that my grid has 7 columns that are identical visually and whose data can be known by an index.
So I want to be able to simplify the xaml by using a subclass DataGridTextColumn which has an index property, and just have:
<DataGrid ... >
<DataGrid.Columns >
<local:DayOfWeekColumn Index="0" />
<local:DayOfWeekColumn Index="1" />
....
<local:DayOfWeekColumn Index="7" />
</DataGrid.Columns >
</DataGrid >
=== REVISED QUESTION ===
Assuming the Binding itself is logically and syntactically correct, what should the parameters to BindingOperations.SetBinding be??
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
var activity = (ActivityViewModel)dataItem;
var cellData = activity.Allocations[Index];
var b = new Binding
{
Source = cellData,
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
Converter = new AllocationAmountConverter()
};
BindingOperations.SetBinding(??, ??, b);
return ??;
}
=== EDITS for ARAN =====
I am not overriding GenerateElement right now, but rather trying to get a static helper to set my binding for me. The helper is needed in any event to compensate for not being able to bind Header content in the current implementation of MSFT's DataGrid.
Basically the idea is to catch the DC from the grid and use it as necessary on each of the columns, which in this case would be the Header content, cell style, and Binding. Here is the code:
public class TimesheetDataGridColumnContextHelper
{
static TimesheetDataGridColumnContextHelper() {
FrameworkElement.DataContextProperty.AddOwner(typeof (DataGridTextColumn));
FrameworkElement.DataContextProperty.OverrideMetadata(
typeof (DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnDataContextChanged));
}
public static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as DataGrid;
if (grid == null || !grid.Name.Equals("adminActivityGrid")) return;
foreach (var col in grid.Columns) {
var dowCol = col as DayOfTheWeekColumn;
if (dowCol == null) continue;
var context = (IActivityCollectionViewModelBase) e.NewValue;
var index = Convert.ToInt32(dowCol.DowIndex);
_setHeader(dowCol, context, index);
var editStyle = (Style) grid.FindResource("GridCellDataEntryStyle");
dowCol.CellStyle = editStyle;
_setBinding(dowCol, index, context);
}
}
private static void _setBinding(DayOfTheWeekColumn dowCol, int index, IActivityCollectionViewModelBase context) {
dowCol.Binding = new Binding
{
Path = new PropertyPath(string.Format("Allocations[{0}]", index)),
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
Converter = new AllocationAmountConverter()
};
}
private static void _setHeader(DataGridColumn col, IActivityCollectionViewModelBase context, int index)
{
var date = context.HeaderDates[index];
var tb = new TextBlock
{
Text = date.ToString(Strings.ShortDayOfWeekFormat),
ToolTip = date.ToLongDateString()
};
col.Header = tb;
}
}
}
Everything works except for the Binding. I can't tell if it's because my binding is wrong somehow (although I get no obvious errors) or this is not a good place to set it. The grid columns are just empty when I run it.
Any idea??
Cheers,
Berryl
=== FIXED! ===
The logic in the last update was actually correct, but getting lost in the internals of the DataGrid I missed that my Binding.Path was missing the property to be bound to! Credit to Aran for understanding the issue, realizing that GenerateElement overrides were not necessary, and catching that the Binding Source should not have been set.
You're always doing the fiddly grid bits eh Beryl?
Do a couple of things. Use reflector to look at the implementation of GenerateElement in the DataGridTextColumn. (.NET programmers live in reflector)
Now for the answer:
In the datagrid each column is not part of the visual tree. The column has two methods GenerateElement and GenerateEditingElement. These methods return the viewer and the editor for the cell respectively. In your method above you are not creating the viewer, which will probably be a TextBlock.
from reflector, the implementation of GenerateElement is as below, notice the first thing they do is create the viewer for the cell.
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
TextBlock e = new TextBlock();
this.SyncProperties(e);
base.ApplyStyle(false, false, e);
base.ApplyBinding(e, TextBlock.TextProperty);
return e;
}
Once you have a textblock you can use the line below to set the binding on it.
BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, binding);
I am not however convinced that you actually need to override the GenerateElement and GenerateEditingElement to get your desired effect. I think you could overide the Binding property of the base class and just modify the binding there with your extra field whenever it is set. This will mean everything else will just work and you wont end up removing functionality from your column. Once again a crawl through reflector looking at the class DataGridBoundColumn (the abstract base class) would be beneficial.
I do something similiar in one of our columns whenever a binding is set I modify the clipboard binding by adding an extra property so I can copy and paste effectively.
EDIT: Update...this should probably be another question but..
You are explicitly setting the source of the binding in your setBinding method. In the grid the source of the binding is the data contained in the row. You are setting it, which means it would be the same for each row. You can apply these funky bindings without the source property before the data context is set, the source becomes the item in each row, and your binding should reflect an index into the property held in each row.
Based on MSDN, it sounds like the first parameter of SetBinding() should be the control that you want to display the binding in (this in this case, assuming that GenerateElement() is a member of the DayOfWeekColumn class), and the second property is the property to bind the data to. I haven't used the WPF DataGrid very much, but I didn't see anything like a text property to set.
I do see that the DataGridTextColumn does have a Binding property, though. Maybe it would work to set it to the binding you created manually above?

Define Background Color of Alternating TreeView Rows Based on Visibility

Is there a way in WPF to define the background of alternating visible rows?
I've tried setting the AlternationCount property, but that restarts for every child node which gives a weird look.
Ideally what I would like is to know what the visual index of a given node is. Only expanded nodes are counted.
There was no easy way to do this as WPF creates nested containers for the tree nodes. So as Rachel mentioned, looping through the items seemed to be the way to go. But I didn't want to depart too much from the built in ItemsControl.AlternationIndex attached property as that is the one people would expect. Because it is readonly I had to access it via reflection, but after that things fell into place.
First off, make sure you handle the Loaded, Expanded and Collapsed events of your TreeViewItem. In the event handler find the owning TreeView and do a recursive alternation count set of all visible nodes. I created an extension method to handle it:
public static class AlternationExtensions
{
private static readonly MethodInfo SetAlternationIndexMethod;
static AlternationExtensions()
{
SetAlternationIndexMethod = typeof(ItemsControl).GetMethod(
"SetAlternationIndex", BindingFlags.Static | BindingFlags.NonPublic);
}
public static int SetAlternationIndexRecursively(this ItemsControl control, int firstAlternationIndex)
{
var alternationCount = control.AlternationCount;
if (alternationCount == 0)
{
return 0;
}
foreach (var item in control.Items)
{
var container = control.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (container != null)
{
var nextAlternation = firstAlternationIndex++ % alternationCount;
SetAlternationIndexMethod.Invoke(null, new object[] { container, nextAlternation });
if (container.IsExpanded)
{
firstAlternationIndex = SetAlternationIndexRecursively(container, firstAlternationIndex);
}
}
}
return firstAlternationIndex;
}
}
As you can see it runs through each node and sets the custom alternation index. It checks if the node is expanded and if so continues the count on the child nodes.
Above I mentioned that you have to handle the Loaded event for the TreeViewItem. If you only handle the expanded and collapsed events you won't get the new containers that are created when a node is first opened. So you have to do a new pass when the child node has been created and added to the visual tree.
Something I've done with javascript is create an OnLoaded event for a table which loops through the table rows and if the row is visible, it sets the background color to a nextColor variable, and changes the nextColor variable to the opposite color. That might work here.

Access DataGridCell's children from another DataGridCell?

I have a DataGridCell that contains a ComboBox.
I want, that when I fire 'SelectionChanged' event of it, a CollectionViewSource of a different column (eventually - at runtime, cell) CellEditingTemplate's Resources should be populated with data according to the selected value for this row.
Maybe DataTrigger, ActionTrigger, EventTrigger, maybe by code, XAML I don't care, I just need a solution.
Thanks a lot!
Related: Accessing control between
DataGridCells, dynamic cascading
ComboBoxes
If I understand your question right, you will fill the contents of a combobox in a cell based on the selection of a combobox in another cell that is in the same row of the DataGrid.
If yes:
First Solution (IMO the preferable)
Make a ViewModel that represents the rows data (a simple wrapper around your data object). Bind the ItemsSource-property of the destination ComboBox to a IEnumerable-property that you provide from your viewmodel.
Bind the SelectedItem from the source-ComboBox to another property of your ViewModel. Every time this source-property changes in your ViewModel, you change the contents of the list that is provided by the ViewModel.
Use for the desintation (list) property a ObservableCollection<T>. The source property is up to you.
Here is an approximately example. I call the class VM (for ViewModel) but this changes nothing on your current solution. MVVM can also be used partial.
public class DataObjectVM : DependencyObject {
public static readonly DependencyProperty SelectedCategoryProperty =
DependencyProperty.Register("SelectedCategory", typeof(CategoryClass), typeof(DataObjectVM), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,delegate (DependencyObject d,DependencyPropertyChangedEventArgs e){
((DataObjectVM)d).SelectedCategoryChanged(e);
}));
ObservableCollection<ItemClass> _items=new ObservableCollection<ItemClass>();
void SelectedCategoryChanged(DependencyPropertyChangedEventArgs e) {
// Change here the contents of the _items collection.
// The destination ComboBox will update as you desire
// Do not change the _items reference. Only clear, add, remove or
// rearange the collection-items
}
// Bind the destination ComboxBox.ItemsSource to this property
public IEnumerable<ItemClass> DestinationItems {
get {
return _items;
}
}
// Bind to this property with the source ComboBox.SelectedItem
public CategoryClass SelectedCategory {
get { return (CategoryClass)GetValue(SelectedCategoryProperty); }
set { SetValue(SelectedCategoryProperty, value); }
}
}
Add a constructor to this class that takes your data object and make some wrapper properties to the rest the properties you need to provide in the DataGrid. If they are alot, you can also make one property that provides your data object and the bind directly to it. Not nice, but it will do the job.
You also can (must) pre-initialize the SelectedCategory with data from your business object. Do this also in the constructor.
As a ItemsSource for the DataGrid you give an IEnumerable of the DataObjectVM-class that wrapps all items you want to show.
Alternative way with VisualTreeHelper
If you want to do it manual, register in the code behind a handler for the ComboBox.SelectionChangedEvent and change then the ItemsSource of the destination ComboBox manual. The business-object you will get with the EventArgs. The destination ComboBox you must search in the visual tree (Use the VisualTreeHelper). The events can be wired also if you use the DataGridTemplateColumn class and add a DataTemplate with the corresponding ComboBoxes.
But I think this is realy not very simple to do and can be error prone. The above solution is much easier.
Here is the code you propably are looking for:
private void CboSource_SelectionChanged(object sender, SelectionChangedEventArgs e) {
ComboBox cbo = (ComboBox)sender;
FrameworkElement currentFe = VisualTreeHelper.GetParent(cbo) as FrameworkElement;
while (null != currentFe && !(currentFe is DataGridRow)) {
currentFe = VisualTreeHelper.GetParent(currentFe) as FrameworkElement;
}
if (null != currentFe) {
List<ComboBox> list = new List<ComboBox>();
FindChildFrameworkElementsOfType<ComboBox>(currentFe,list);
// Requirement 1: Find ComboBox
foreach (ComboBox cboFound in list) {
if (cboFound.Name == "PART_CboDestination") {
// This is the desired ComboBox
// Your BO is available through cbo.Found.DataContext property
// If don't like to check the name, you can also depend on the
// sequence of the cbo's because I search them in a deep search
// operation. The sequence will be fix.
}
}
List<DataGridCell> cells = new List<DataGridCell>();
FindChildFrameworkElementsOfType<DataGridCell>(currentFe,cells);
// Requirement 2: Find Sibling Cell
foreach (DataGridCell cell in cells) {
// Here you have the desired cell of the other post
// Take the sibling you are interested in
// The sequence is as you expect it
DataGridTemplateColumn col=cell.Column as DataGridTemplateColumn;
DataTemplate template = col.CellTemplate;
// Through template.Resources you can access the CollectionViewSources
// if they are placed in the CellTemplate.
// Change this code if you will have an edit cell template or another
// another construction
}
}
}
void FindChildFrameworkElementsOfType<T>(DependencyObject parent,IList<T> list) where T: FrameworkElement{
DependencyObject child;
for(int i=0;i< VisualTreeHelper.GetChildrenCount(parent);i++){
child = VisualTreeHelper.GetChild(parent, i);
if (child is T) {
list.Add((T)child);
}
FindChildFrameworkElementsOfType<T>(child,list);
}
}
And this is the markup I used:
<DataGrid.Columns>
<DataGridTemplateColumn Header="Source" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PART_CboSource" SelectionChanged="CboSource_SelectionChanged" ItemsSource="!!YOUR ITEMS SOURCE!!" SelectedItem="{Binding Category}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Destination">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PART_CboDestination"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Accessing the CollectionViewSource
To access the CollectionViewSource, put it into the resources section of the corresponding DataTemplate, not of the panel, then you will have direct access to them. IMO is this location anyway more appropriate than the resources-container of the grid.
If you dont't want to do this, check the state of the following post:
How to get logical tree of a DataTemplate

WPF TreeView databinding to hide/show expand/collapse icon

I implemented a WPF load-on-demand treeview like described in this (very good) article.
In the mentioned solution a dummy element is used to preserve the expand + icon / treeview item behavior. The dummy item is replaced with real data, when the user clicks on the expander.
I want to refine the model by adding a property public bool HasChildren { get { ... } } to my backing TreeNodeViewModel.
Question:
How can I bind this property to hide/show the expand icon (in XAML)? I am not able to find a suitable trigger/setter combination.
(INotifyPropertyChanged is properly implemented.)
I want to use my property public bool HasChildren instead of using the dummy element.
Determining whether or not an item has children is somewhat costly, but still much cheaper than fetching the children.
Julian,
this is a really good question. Why don't you try to write your own tree view item? :) I mean, not from the scratch, just derive from existing TreeViewItem and add your property. I have prepared a quick sample, but feel free to modify it as you wish (and ask questions if something is not perfectly clear). here we go:
public class TreeViewItem_CustomControl : TreeViewItem
{
static TreeViewItem_CustomControl()
{
HasChildrenProperty = DependencyProperty.Register("HasChildren", typeof(Boolean), typeof(TreeViewItem_CustomControl));
}
static DependencyProperty HasChildrenProperty;
public Boolean HasChildren
{
get
{
return (Boolean)base.GetValue(HasChildrenProperty);
}
set
{
if (value)
{
if (this.Items != null)
{
this.Items.Add(String.Empty); //Dummy item
}
}
else
{
if (this.Items != null)
{
this.Items.Clear();
}
}
base.SetValue(HasChildrenProperty, value);
}
}
}
This was the code for your custom TreeViewItem. Now let's use it in XAML:
<TreeView>
<TreeViewItem Header="qwer">
Regulat tree view item.
</TreeViewItem>
<CustomTree:TreeViewItem_CustomControl x:Name="xyz" Header="temp header" Height="50">
<TreeViewItem>Custom tree view item, which will be removed.</TreeViewItem>
</CustomTree:TreeViewItem_CustomControl>
</TreeView>
As you can see, first item is a regular one and the second is your custom one. Please note, that it has one child. Next, you can bind HasChildren property to some Boolean object in your ViewModel or just simply test my custom class by setting HasChildren to False from code behind the above XAML:
xyz.HasChildren = false;
Now, despite your element has one child, expand button is not displayed, so it means, that my custom class works.
I hope I helped you, but feel free to ask if you have any questions.
Piotr.
After quickly looking into Josh's code, I found this constructor:
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
So, if you pass false for the lazyLoadChildren parameter from your inheriting ViewModel classes, the + icon should not appear because the DummyChild is not added. Since you seem to know whether your items have children or not, you should be able to pass the appropriate value for the lazyLoadChildren property. Or am I missing something?

Resources