0

I'm creating an OverflowPanel derived from the WPF Panel class. The intent is that it will fill with items in a single direction, and when there are too many items to display, excess items will be removed and replaced with another control to hold the overflow. Think of a website's breadcrumbs, or the address bar in Windows File Explorer. This is a .Net Core 3/C# 8 project.

I have a partially working solution: I've inherited from Panel and overridden MeasureOverride() and ArrangeOverride() to get the behavior I want. My problem now is getting a button or some other control to display in place of the items being removed.

My initial, naive approach was to just create a Button in code and try to Measure/Arrange it.

public class OverflowPanel : Panel
{
    // First by itself, but I did also try to host this in a new UIElementCollection
    private readonly Button _overflowButton = new Button();

    public override Size MeasureOverride(Size availableSize)
    {
        ...
        _overflowButton.Measure(availableSize);
        // Do stuff with _overflowButton.DerivedSize.
        ...
    }

    // Also attempted to draw int in ArrangeOverride()
}

This did give me non-zero result for the measurement. (I put some dummy content in the button.) My algorithm gives me space on the screen where the button should go, however, nothing gets rendered there.

I also confirmed that there wasn't simply a button being drawn with no visual style, by inspecting the Live Visual Tree in Visual Studio.

I tried to make a UIElementCollection and add the button to that to see if it would add it to the visual tree, but this also did not work.

Most Google/StackOverflow results I've seen suggest something along the lines of this.Children.Add(_overflowButton), but this does not work when hosted inside an ItemsControl, as it takes over managing the collection of objects and throws an exception if you attempt to mess with it.

After digging around in the code for Panel and UIElementCollection, I noticed that Panel lets you override

UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)

to use a derived implementation of UIElemenetCollection. I created a PinningUIElementCollection to trick WPF into rendering the extra element. It stores extra items and then slips them in whenever the iterator is accessed. It also does index mangling to access both the extra collection of items and the automatically generated one.

This actually worked. My button is now displayed (albeit without the correct styling, but that's a separate issue.)

However my issue with this approach is that it seems like a lot of work. It also seems error prone: I could easily miss when it tries to use a numerical index and forget to mangle it, causing unpredictable results.

Is there a simpler/more straightforward way, in my derived Panel implementation, to display an extra button or some other arbitrary control with only a few less hoops?

Alphacat
  • 323
  • 2
  • 8
  • 1
    I think extending `Panel` is the wrong way to achieve your desired behavior. As you found out by yourself `ItemsControl` manages the items displayed or contained by the `Panel` by using the `ItemContainerGenerator`. The `Panel` itself is only responsible for layout (e.g., `StackPanel`) or virtualization (e.g., `VirtualizingPanel`). The `VirtualizingPanel` uses the `ItemContainerGenerator` to control which item containers are generated but is not able to add or remove items as this would mean to manipulate the `ItemsControl.Items` collection. – BionicCode Nov 11 '19 at 08:04
  • To do this you need to move responsibilities to the `ItemsControl` i.e you have to create or extend your own `ItemsControl`. Now you can add or remove items from the source which are then added to the underlying `Panel`. You would do this by providing a `DataTemplate` for your placeholder item which would render this item e.g. as a `Button`. You can overrride the `ArrangeOverride` of the `ItemsControl` to arrange items. – BionicCode Nov 11 '19 at 08:04
  • You can then create all types of UIElement items this way: define a data type and add it to the `ItemsSource`. Define a `DataTemplate` for this type that controls how this data is rendered or in your situation "replaced" (e.g. `ToggleButton`, `Image`, custom control, etc). – BionicCode Nov 11 '19 at 08:09
  • @BionicCode You know, looking ahead at what my next steps would have been, I think you're right. After this I would have needed to create a popup or some other element to put the overflowed children on, but I would probably have issues shuffling things around between different parent elements without being the ItemsControl in charge of it. I think I'll go ahead and do that instead of my original approach. – Alphacat Nov 11 '19 at 16:08
  • Sounds like should extend `ItemsControl` (or `ListView`) and override the default `ControlTemplate` and add a second nested `ItemsControl` to it in order to create an overflow panel. So your `ItemsControl` would have a second maybe internal collection e.g. `OverflowPanelItemsSource` that serves as the `ItemsSource` for the nested `ItemsControl` (overflow panel). You can toggle the `Visibility` of this overflow panel on mouse over or button click. – BionicCode Nov 11 '19 at 16:17

0 Answers0