I created a solution as a subclass of DockPanel, but I do not mark this as an accepted answer, because I hope to find a way to do this with styles or attached behaviors so that it can be used with any DockPanel (or another subclass), and not just that.
However, for others, this may be useful, so I post it here.
Below is the code for the full class. The meat of the job is in the ArrangeOverride method, which was based on the original logic from the DockPanel extracted from Reflector.
The way the existing logic worked was inside ArrangeOverride, if LastChildFill was set, it saved the index of the last child (i.e. the index of the element to be filled) in the variable. If LastChildFill has not been set, it instead stores "count" in this variable.
Then, when passing through the child elements that perform the actual arrangement, if the ordered element had an index smaller than the previously stored index, it executed the “docking” logic, otherwise it would execute the “fill” logic.
This meant that when LastChildFill was false, each element triggered a “docking” logic, since they all had an index below that stored index (which again equals “count” or the highest index + 1). However, when LastChildFill was true, the last element did not have an index smaller than the stored index (it was actually equal to it), so that one element executed the "fill" logic, and everything else performed the "docking" logic.
The change I made was installed, if LastChildFill was set, as indicated above, the index is stored, pointing to the last child, but then I check the visibility of this element, and if it is invisible, I drop the index by one and check again, continuing, while I do not find a visible element, or I do not run away from children to check (i.e. if they were invisible). That is why I named the variable "firstFilledIndex" so technically that all the elements subsequently use "Fill", the logic, although all the elements after it are invisible.
Finally, I added a new LastVisibleChildFill property to enable or disable my new behavior. As an aid to consumers, if you set it to true, it will also implicitly set LastChildFill to true.
Here is the full code.
public class DockPanelEx : DockPanel { public static readonly DependencyProperty LastVisibleChildFillProperty = DependencyProperty.Register( "LastVisibleChildFill", typeof(bool), typeof(DockPanelEx), new UIPropertyMetadata(true, (s,e) => { var dockPanelEx = (DockPanelEx)s; var newValue = (bool)e.NewValue; if(newValue) dockPanelEx.LastChildFill = true; // Implicitly enable LastChildFill // Note: For completeness, we may consider putting in code to set // LastVisibileChildFill to false if LastChildFill is set to false })); /// <summary> /// Indicates that LastChildFill should fill the last visible child /// Note: When set to true, automatically also sets LastChildFill to true as well. /// </summary> public bool LastVisibleChildFill { get { return (bool)GetValue(LastVisibleChildFillProperty); } set { SetValue(LastVisibleChildFillProperty, value); } } protected override Size ArrangeOverride(Size totalAvailableSize) { UIElementCollection internalChildren = base.InternalChildren; int count = internalChildren.Count; int firstFilledIndex = count; if(LastChildFill) { for(firstFilledIndex = count - 1; firstFilledIndex >= 0; firstFilledIndex--) { if(!LastVisibleChildFill || internalChildren[firstFilledIndex].IsVisible) break; } } double usedLeftEdge = 0.0; double usedTopEdge = 0.0; double usedRightEdge = 0.0; double usedBottomEdge = 0.0; for (int i = 0; i < count; i++) { UIElement element = internalChildren[i]; if (element != null) { Size desiredSize = element.DesiredSize; var finalRect = new Rect( usedLeftEdge, usedTopEdge, Math.Max(0.0, (totalAvailableSize.Width - (usedLeftEdge + usedRightEdge))), Math.Max(0.0, (totalAvailableSize.Height - (usedTopEdge + usedBottomEdge)))); if (i < firstFilledIndex) { switch (GetDock(element)) { case Dock.Left: usedLeftEdge += desiredSize.Width; finalRect.Width = desiredSize.Width; break; case Dock.Top: usedTopEdge += desiredSize.Height; finalRect.Height = desiredSize.Height; break; case Dock.Right: usedRightEdge += desiredSize.Width; finalRect.X = Math.Max((double) 0.0, (double) (totalAvailableSize.Width - usedRightEdge)); finalRect.Width = desiredSize.Width; break; case Dock.Bottom: usedBottomEdge += desiredSize.Height; finalRect.Y = Math.Max((double) 0.0, (double) (totalAvailableSize.Height - usedBottomEdge)); finalRect.Height = desiredSize.Height; break; } } element.Arrange(finalRect); } } return totalAvailableSize; } }