I have an expander that has n contained elements (possibly other Expanders that also contain elements).
Now I want to programmatically bring a contained element into view - like with BringIntoView() for ScrollViewers. All Expanders that currently hide the element should expand.
My current idea is to subclass the Expander and make it react to an event that bubbles up from the contained element. But there may be a much easier way in WPF, right?
You can create an attached property to do that instead of subclassing the existing Expander class. This AP would be of type bool, and when sets to True on an expander register for the Expanded event. In the event handler, you can walk up the logical tree to grab the parent Expander and toogle is IsExpanded property.
Related
I have a ListBox which is not virtualizing. I am obviously missing something, but cannot find it.
It's actually a ListBox within a ListBox. The outer ListBox has an ItemTemplate which contains an Expander. The Expander is used to display a group of items. The content of the Expander is the second ListBox which displays the items. The ItemTemplate on the second ListBox is bound to the actual Item to display. The ViewModel class for my items has a number of properties which do not initialize data until the property Get is called. However, WPF is walking through every item and causing initialization logic, which I'm trying to avoid.
I discovered that using the ListCollectionView.GroupDescription causes a ListBox to not Virtualize. Now I am handling my own grouping in the ViewModel. My outer ListBox is bound to an ObservableCollection(ItemGroupViewModel), where ItemGroupViewModel has a GroupName and a list of Items. The second ListBox is bound to the Items in the ItemGroupViewModel. This did not fix the problem. I also checked VirtualizingStackPanel.GetIsVirtualizing() while debugging and it returns true, but WPF is still walking through every item. I've double checked to ensure my grouping logic does not fire off the data initialization logic in each item, which I'm trying to avoid.
I thought the IsSharedSizeScope could cause the ListBox to render all items in order to determine column sizing. So I turned SharedSizeScope off. Still no virtualization.
What am I missing?
It is not virtualizing because it is inside an expander, the expander's template is a headeredcontentcontrol (as far as I remember), and it's template contains a stackpanel.
Contents of stackpanel can never have alignement stretch in the direction it is stacking, so your listbox will always have all the room it asks for, and then you get no virtualization. Your inner listbox won't get any verticalscrollbar either.
You have one solution (at least :)
1: Set maxheight on your inner listbox - probably the easiest.
Hope it helps :)
I have a ScrollViewer and a ListBox inside it which is bound to an ObservableCollection in the view model. The ScrollViewer is maximized to take up all available space of the parent container. I'm finding that when the collection is modified and ends up producing more ListBoxItems than can fit in the viewable area of the ScrollViewer, the ScrollViewer scrolls down to show the last item in the ListBox. How do I prevent the ScrollViewer from scrolling when the child ListBox's items are updated?
I would like the scroll position to stay intact whenever the collection in the view model is updated.
Thanks in advance!
You are going to have to manage this yourself. The ListBox has a ScrollIntoView method that allows you to scroll to a specific location:
http://msdn.microsoft.com/en-us/library/system.windows.controls.listbox.scrollintoview(v=VS.95).aspx
Determining the items that are currently visible, if you need this, is not so easy. See the ItemsControlExtensions that I wrote as part of the WP7Contrib project:
http://wp7contrib.codeplex.com/SourceControl/changeset/view/67473#1475881
This has a GetItemsInView extensions method that will provide the list of visible items.
Is there any property or state I can set on a expander object that makes it unexpandable but still can be selected?
I'm thinking for example of the last Child in a TreeView where I don't wan't the Expander to have an expander button. But other uses could be if someone doesn't have access to more then the header or something along that line.
The only way I know would be to create custom control template for expander, remove expander button from it and disable expanding.
See WPF Expander: Reversing the icon direction while keeping the content location (ExpandDirection) the same on how to create custom template for expander.
I have a custom panel for a listbox
<ItemsPanelTemplate x:Key="FloatPanelTemplate">
<Controls:FloatPanel x:Name="CardPanel" />
</ItemsPanelTemplate>
The panel lays out its children using its X and Y dependency properties.
This all works nicely when the FloatPanel is used by itself - I'm using FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure on the dependency properties of the child items to tell the FloatPanel to redraw its layout.
When I use it in a Listbox (code above) then it draws fine the first time, but when I drag the children (which modifies the item's X and Y) it is not notifying the Listbox that it needs to redraw the FloatPanel's children. I think the issue is related to the fact that each item in the bound collection is wrapped with a ListBoxItem.
Hopefully I've described what i'm doing well enough that someone can tell me how to make the panel (or its children) tell it needs to do the Layout routines again. As I said it works once (initial draw) but then dragging items doesn't work (Listbox isnt aware that its children have changed and needs to relayout.) If I drag an item and then resize the window, the listbox does a layout and the items are drawn in their new locations.
How do I notify the ListBox (or more importantly the FloatPanel in the ItemsPanelTemplate) that it needs to do a Layout pass?
Instead try FrameworkPropertyMetadataOptions.AffectsParentMeasure and FrameworkPropertyMetadataOptions.AffectsParentArrange.
Those names... Thank God for intellisense, huh?
As you've noted, since ListBoxItem is your element's immediate layout parent, changes to the dependency properties that affect the parent's layout will not be "seen" by the panel which is further up the visual tree.
So instead what you may need to do unfortunately is to traverse the visual tree until you find an element deriving from Panel and call its InvalidateArrange method.
DependencyObject obj=this;
while ( (obj=VisualTreeHelper.GetParent(obj)) != null) {
Panel p = obj as Panel;
if (p != null) {
p.InvalidateArrange();
break;
}
}
It's ugly, but maybe a WPF guru will have a better suggestion.
Are you sure that your ListBox or ListView panel isn't seeming to present an incorrect size because it has...
ScrollViewer.CanContentScroll="True"
...which is the default value?
When list control is in this mode, and when it is scrolled to the bottom, there may be extra whitespace at the bottom of its panel in order to make sure that an integral item is lined up at the top of its internal ScrollViewer, and this might seem like the ScrollViewer--and hence the list control--is not resizing itself.
To prevent whitespace at the bottom of the panel, you must enable pixel-wise scrolling on the ListBox or ListView control:
ScrollViewer.CanContentScroll="False"
or in code:
ScrollViewer.SetCanContentScroll(list_ctrl, false);
See also: WPF ListView non integral scrolling
Listbox has the style in which itemtemplate consists of expander and inside expander there is a one more listbox, listbox inside the expander has to be accessed. So how to go ahead?
What data does the inner list box contain? Does your outer object contain a list of child objects as a property? If so, bind the inner listbox's ItemsSource to that property.
If you want to access the listbox from code, this is probably what you're looking for: How to: Find DataTemplate-Generated Elements. It shows how to traverse the visual tree to find the generated elements. Using the visual tree is not a good solution usually though, so consider using data binding instead if at all possible.