0
votes

I have a ListBox whose Style and ItemTemplate I change in code-behind on click of specific buttons.

listbox.ItemTemplate = FindResource("dataTemplateView1") as DataTemplate;
listbox.Style = FindResource("listBoxStyle1") as Style;

There are three possible views so there are three sets of data template and style. The DataTemplate contains some text and thumbnails (different sizes per set). The styles just change the ItemsPanelTemplate to WrapPanel, StackPanel (Horizontal) and StackPanel (Vertical). Example:

<Style x:Key="listBoxStyle1" TargetType={x:Type ListBox}">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
               <WrapPanel />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>

The problem is that when I have a selected item (say item with index 20) and I change the view, the visible area of the listbox shown will reset back to the first index (but the selected item is still selected, just not shown).

I tried to solve this using the solution here to scroll to the selected item and set it to center. But currently, there are times that the behavior seems choppy since what happens is that the first item in listbox is shown first then it jumps to the selected item. Any other alternatives for this?

Thanks!

2

2 Answers

1
votes

What you are looking for is not too different from what I wrote in my earlier answer.

Two differences are:

  1. Your goal seems to be to keep the current item from moving, not to get it to move to the center. This is actually possible and is just as easy as centering it.
  2. The timing of your code is allowing the un-scrolled version to be displayed momentarily, causing jumpiness.

Both of these problems can be solved together as follows (reference my earlier answer for the relevant code details):

  1. Use box.ItemContainerGenerator.ContainerFromItem(box.SelectedItem) to get the item container
  2. Call container.TransformToAncestor(box).Transform(new Point()) to get the container's upper-left corner relative to the list box. (If you prefer to keep the center of the item stationary, use new Point(container.DesiredSize.Width/2, container.DesiredSize.Height/2) instead.)
  3. Change the ItemTemplate or Style, as desired
  4. Force an immediate re-measure (the easy way to do this is by calling UpdateLayout)
  5. Use box.ItemContainerGenerator.ContainerFromItem(box.SelectedItem) again to get the item container
  6. Call container.TransformToAncestor(box).Transform(new Point()) again to get the new container's upper-left corner relative to the list box
  7. Use the code from my other answer to find the IScrollInfo and update the HorizontalOffset and VerticalOffset to bring the new container to the same position as the old one.

The reason this will work without flicker is that everything here happens synchronously, so no dispatcher operations at Render priority will run. The cost is that UpdateLayout() is comparatively expensive.

Also note that if any of your individual items is implemented such that it changes its size after the initial measure/arrange (eg in a dispatcher callback), then you will need to re-position twice: Once in synchronous code, then again in a dispatcher callback. In this case the only sure-fire general method I know to prevent flicker is to fix the implementation of the items to get their size right before the initial layout is complete.

0
votes

This is a matter of taste, but I'd trigger fade-out and fade-in animations of perhaps .15 or .2 second, and do the template-switch while it is faded out. This will both conceal the scrolling and smooth out the otherwise very abrupt switch in layout.