ChromeTabControl and Visual Children in WPF
I’ve renamed the classes, projects, and solution for the project because I didn’t want to infringe on Google’s IP. Thank you, philipmat, for pointing out their trademark and thank you, Google, for letting me search out the particulars of your intellectual property.
Since Friday, I’ve committed
- Changed mode on most files. Make resizing work correctly.
- Make the unselectable tab selectable (a tab with no ChromeTabItem as the child)
- Make the little round button for closing
- Remove objects from the mapping dictionary to prevent “memory leaks”
- Now tabs close nicely for the tab control
- AddTab/RemoveTab functionality demonstrated
- Moved the responsibility of dragging tabs from the tab item to the tab panel
- Added “Close selected tab” button on test window
- Draw the “add tab” button and have it react to mouse over for color change
- Wire up the add button to add a tab
Those last two commits demonstrated a feature that I knew about WPF but never had use to exercise: the difference between the logical tree and the visual tree.
Right now, the ChromeTabPanel
acts as the item host for the
ChromeTabControl
which means that the ChromeTabPanel
acts as the panel
that displays the children on behalf of the ChromeTabControl
. In this case,
that means a lot of ChromeTabItem
s.
With that coöperation occuring, the content of ChromeTabPanel
‘s Children
property contains the enumeration of ChromeTabItem
s that appear as the tabs
thanks to the Style
s found in the Generic.xaml file. While that works
well, I wanted the ChromeTabPanel
to also manage and display a button that
the user could click to add a new tab, just like you find in Google Chrome.
I didn’t want to add that to the Children
list because that would pollute
the content and intent of that collection. I needed something else. I needed
the visual tree instead of the logical tree.
First, I defined a new Style
for the add button in the XAML theme file.
1 | <Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:ChromeTabPanel}, ResourceId=addButtonStyle}" TargetType="{x:Type Button}"> |
Then, I created a field for it and its size in the ChromeTabPanel
class and
initialized it in the constructor with the style from the theme file.
1 | ComponentResourceKey key; |
Now, I want it to pariticpate in the measure/arrange phase of WPF rendering.
I added the calculations to the ArrangeOverride
and MeasureOverride
methods in the ChromeTabPanel
.
1 | protected override Size ArrangeOverride(Size finalSize) |
At this point, I really thought it would work. I’ve included the add button in the measure/arrange cycle.
It did not work. Sadness descended upon me like an unkindness of ravens. :(
Then, hope struck me. I found the Visual.AddVisualChild
method! Woot! I
added a call in the constructor to add the button as a visual child. And…
…nothing. What the? Off to Googleland, again, where I found
this thread on MSDN.
Turns out that AddVisualChild
really doesn’t do anything except send a
notification to the base class that you’ve acquired a new child in the
visual element’s child collection! Kind of a badly named method, if you ask
me.
The thread suggests, instead, overriding the VisualChildrenCount
property
and the GetVisualChild
method. I did that with the expectation that the
add button should always appear as the last visual child in the visual
children collection.
1 | // In ChromeTabPanel |
And, by Grabthar’s hammer, by the suns of Warvan, it worked! Now, I have a visual but not a logical child.
The downside to this implementation: I have to do the mouse event handling because routed event propagation occurs only in the logical tree. Not a big deal. I have introduced a refactoring opportunity with the wedged-in implementation that currently exists.
Again, if you feel like helping out or reviewing the code for suggestions, head over to the GitHub repo and do the magic that we call “development!”