Answered by:
[WPF] Toolbar Overflow Issue (.NET 3.5)

Question
-
Hi,
I've found an issue on deleting items in a Toolbar with Overflow.
Here are the samples (the zip with the XAML, C# and EXE is here: )
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="WpfApplication1.Window1" ResizeMode="CanResize" SizeToContent="WidthAndHeight"> <Grid> <ToolBar x:Name="toolbar" ItemsSource="{Binding}" /> </Grid> </Window> namespace WpfApplication1 { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { private ObservableCollection<Button> _ocButtons = new ObservableCollection<Button>(); public Window1() { InitializeComponent(); this.DataContext = _ocButtons; for (int i = 0; i < 10; i++) { Button b = new Button { Height = 23, Width = 72, Content = "Bouton " + i }; b.Click += new RoutedEventHandler((sender, e) => _ocButtons.Remove((Button)sender)); //b.Click += new RoutedEventHandler((sender, e) => ((Button)sender).Visibility = Visibility.Collapsed); _ocButtons.Add(b); } } } } Now, if you try to delete a button in the "Overflow Popup", then the Popup becomes always empty even if there are items in it... Then, if you resize the toolbar (by resizing the window), the hidden buttons will appear in the Toolbar but the Popup is still empty.
Uncommenting the commented line, the behavior is quite the same but the Popup appears empty only when you clicked all items in it...
Any ideas to resolve this issue?
Thanks
- Moved by nobugz Thursday, January 29, 2009 11:19 AM wpf q (Moved from .NET Base Class Library to Windows Presentation Foundation (WPF))
Thursday, January 29, 2009 9:50 AM
Answers
-
I build a WPF project the same as yours after reading your latest post. At last, I found the issue you mentioned in the first thread. When I click the Button in the ToolBarOverflowPanel of ToolBar, the Button try to delete its self and the OverFlowPanel become empty. But in fact there still should be some Buttons in the OverFlowPanel.
I can reproduce the above issue many times with your steps and I have to admit that the behavior is less desirable. You can report this issue at Feedback .
Besides giving a feedback, Marco debugged your program and fined a workaround to solve the “Empty OverFlowPanel” problem of ToolBar. I will introduce this solution below.
First, we have to know that there are two important Panels in a WPF ToolBar ToolBar: ToolBarPanel and ToolBarOverflowPanel . WPF use ToolBarOverflowPanel to arrange overflow ToolBar items. The ToolBarPanel determines which ToolBar items will fit inside a ToolBar and which must be put in the overflow area. When you change the items in the ToolBarPanel or resize the window, ToolBar will call Measure() and Arrange() method and update its layout. The key issue is when you click the Button in the ToolBarOverflowPanel, WPF layout system “think” there is no change in UpdateOverflowPanel. So the WPF layout system will skip actual Measure and Arrange steps. In other words, ToolBarOverflowPanel is not updated.
Second, we can find a method directly: force the ToolBarOverflowPanel to layout constrainedly. We can use InvalidateMeasure() method to achieve this easily.
toolBarOverflowPanel.InvalidateMeasure();
Third, we have to solve the problem of finding the ToolBarOverflowPanel in toolbar. We can use GetTemplateChild method to find it. Another problem is what its name is. If you use XamlHack.exe to view the Template of ToolBar, you will find the code below:
<Popup IsOpen="False" Placement="Bottom" StaysOpen="False" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" AllowsTransparency="True" Name="OverflowPopup" Focusable="False"> <mwt:SystemDropShadowChrome Color="#00FFFFFF" Name="Shdw"> <Border BorderThickness="1,1,1,1" BorderBrush="#FF666666" Background="#FFF9F8F7"> <ToolBarOverflowPanel WrapWidth="200" Name="PART_ToolBarOverflowPanel" Margin="2,2,2,2" FocusVisualStyle="{x:Null}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" Focusable="True" KeyboardNavigation.TabNavigation="Cycle" KeyboardNavigation.DirectionalNavigation="Cycle" /> </Border> </mwt:SystemDropShadowChrome> </Popup>
So ToolBarPanel’s name: “PART_ToolBarPanel”. Use similar method, we can get ToolBarOverflowPanel’s name: “PART_ToolBarPanel”.
toolBarPanel = base.GetTemplateChild("PART_ToolBarPanel") as ToolBarPanel; toolBarOverflowPanel = base.GetTemplateChild("PART_ToolBarOverflowPanel") as ToolBarOverflowPanel;
Fourth, we warp the method in a custom class “MyToolBar”. After all, we are hacking ToolBar, so we want the code to be as neatness as possible.
The attached code segment is the source code of my demo project. Hope it helps.
Please feel free to ask any questions if you meet problems.
Best regards!
----------------------------------------------------------
Please mark this thread as answer if you think it is helpful.
Thank you!
- Proposed as answer by Tao Liang Wednesday, February 4, 2009 10:26 AM
- Edited by Tao Liang Wednesday, February 4, 2009 10:37 AM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 1:57 PM
- Unmarked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:34 PM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 10:22 AM -
XAML:
<Window x:Class="_temple.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:_temple" Title="Window1" Height="400" Width="500"> <Grid> <local:MyToolBar x:Name="toolbar" Height="66" Margin="0,0,59,0" VerticalAlignment="Top" HorizontalAlignment="Stretch"> <local:MyToolBar.ItemTemplate> <DataTemplate> <Button Width="50" Height="20" Content="{Binding}" Click="ResetItemSource"/> </DataTemplate> </local:MyToolBar.ItemTemplate> </local:MyToolBar> </Grid> </Window>
- Proposed as answer by Tao Liang Wednesday, February 4, 2009 10:26 AM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 1:57 PM
- Unmarked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:35 PM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 10:23 AM -
C#:
using System; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Controls; using System.Collections; namespace _temple { public partial class Window1 : Window { ArrayList al = new ArrayList(); public Window1() { InitializeComponent(); for (int i = 0; i < 10;i ++ ) { al.Add( "Button"+i.ToString() ); } toolbar.ItemsSource = al; } private void ResetItemSource(object sender, RoutedEventArgs e) { Button btn = (Button)sender; string strBtn =btn.Content.ToString(); for (int i = 0; i < al.Count; i++) { string strI = al[i].ToString(); if (strI == strBtn) { al.RemoveAt(i); break; } } toolbar.ItemsSource = null; toolbar.ItemsSource = al; } } public class MyToolBar : ToolBar { private ToolBarPanel toolBarPanel; private ToolBarOverflowPanel toolBarOverflowPanel; protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) { if (toolBarPanel != null && toolBarOverflowPanel != null) { UpdateToolBarLayout(); } } } public override void OnApplyTemplate() { base.OnApplyTemplate(); toolBarPanel = base.GetTemplateChild("PART_ToolBarPanel") as ToolBarPanel; toolBarOverflowPanel = base.GetTemplateChild("PART_ToolBarOverflowPanel") as ToolBarOverflowPanel; } private void UpdateToolBarLayout() { toolBarPanel.LayoutUpdated += UpdateOverflowPanel; } public void UpdateOverflowPanel(object sender, EventArgs args) { toolBarOverflowPanel.InvalidateMeasure(); toolBarPanel.LayoutUpdated -= UpdateOverflowPanel; } } }
- Proposed as answer by Tao Liang Wednesday, February 4, 2009 10:26 AM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 1:57 PM
- Unmarked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:35 PM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 10:23 AM -
Nice, it work perfectly!
Thank you for all!
EDIT:
I remove the "if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)" statement because the bug appears when you delete some items in the OverflowPanel and if you add some items after!- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 1:59 PM
All replies
-
The fastest way to solve the problem is setting _ocButtons to be the toolbar's ItemsSource.
C#:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.ComponentModel; using System.Collections.ObjectModel; namespace _temple { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { private ObservableCollection<Button> _ocButtons = new ObservableCollection<Button>(); public Window1() { InitializeComponent(); for (int i = 0; i < 10; i++) { Button b = new Button { Height = 23, Width = 72, Content = "Bouton " + i }; Size x = new Size(72, 23); Size y = new Size(80, 30); b.Click += new RoutedEventHandler((sender, e) => _ocButtons.Remove((Button)sender)); _ocButtons.Add(b); } toolbar.ItemsSource = _ocButtons; } } } - Marked as answer by Tao Liang Monday, February 2, 2009 9:36 AM
- Unmarked as answer by Chris_LaFouine Monday, February 2, 2009 12:37 PM
Monday, February 2, 2009 9:36 AM -
Thanks for your answer but your solution does not solve the problem...
When you launch the application, you have that: Click Me! (Screenshot 01)
Now, I'll delete the "Button 3" and I have that: Click Me! (Screenshot 02)
As you can see, the popup is empty, we have only a little empty square but the "Button4 to Button9" items are still in the Toolbox because if I resize it, I'll have that: Click Me! (Screenshot 03)
The problem I have is on deleting items which are in the ToggleButton's Popup! If I delete an item, the ToggleButton's Popup will collapse and then there's no way to see the hidden buttons via clicking the ToggleButton.
Hope this post is helpful.
Monday, February 2, 2009 12:52 PM -
oh I understand the problem now.Tuesday, February 3, 2009 1:47 AM
-
I build a WPF project the same as yours after reading your latest post. At last, I found the issue you mentioned in the first thread. When I click the Button in the ToolBarOverflowPanel of ToolBar, the Button try to delete its self and the OverFlowPanel become empty. But in fact there still should be some Buttons in the OverFlowPanel.
I can reproduce the above issue many times with your steps and I have to admit that the behavior is less desirable. You can report this issue at Feedback .
Besides giving a feedback, Marco debugged your program and fined a workaround to solve the “Empty OverFlowPanel” problem of ToolBar. I will introduce this solution below.
First, we have to know that there are two important Panels in a WPF ToolBar ToolBar: ToolBarPanel and ToolBarOverflowPanel . WPF use ToolBarOverflowPanel to arrange overflow ToolBar items. The ToolBarPanel determines which ToolBar items will fit inside a ToolBar and which must be put in the overflow area. When you change the items in the ToolBarPanel or resize the window, ToolBar will call Measure() and Arrange() method and update its layout. The key issue is when you click the Button in the ToolBarOverflowPanel, WPF layout system “think” there is no change in UpdateOverflowPanel. So the WPF layout system will skip actual Measure and Arrange steps. In other words, ToolBarOverflowPanel is not updated.
Second, we can find a method directly: force the ToolBarOverflowPanel to layout constrainedly. We can use InvalidateMeasure() method to achieve this easily.
toolBarOverflowPanel.InvalidateMeasure();
Third, we have to solve the problem of finding the ToolBarOverflowPanel in toolbar. We can use GetTemplateChild method to find it. Another problem is what its name is. If you use XamlHack.exe to view the Template of ToolBar, you will find the code below:
<Popup IsOpen="False" Placement="Bottom" StaysOpen="False" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" AllowsTransparency="True" Name="OverflowPopup" Focusable="False"> <mwt:SystemDropShadowChrome Color="#00FFFFFF" Name="Shdw"> <Border BorderThickness="1,1,1,1" BorderBrush="#FF666666" Background="#FFF9F8F7"> <ToolBarOverflowPanel WrapWidth="200" Name="PART_ToolBarOverflowPanel" Margin="2,2,2,2" FocusVisualStyle="{x:Null}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" Focusable="True" KeyboardNavigation.TabNavigation="Cycle" KeyboardNavigation.DirectionalNavigation="Cycle" /> </Border> </mwt:SystemDropShadowChrome> </Popup>
So ToolBarPanel’s name: “PART_ToolBarPanel”. Use similar method, we can get ToolBarOverflowPanel’s name: “PART_ToolBarPanel”.
toolBarPanel = base.GetTemplateChild("PART_ToolBarPanel") as ToolBarPanel; toolBarOverflowPanel = base.GetTemplateChild("PART_ToolBarOverflowPanel") as ToolBarOverflowPanel;
Fourth, we warp the method in a custom class “MyToolBar”. After all, we are hacking ToolBar, so we want the code to be as neatness as possible.
The attached code segment is the source code of my demo project. Hope it helps.
Please feel free to ask any questions if you meet problems.
Best regards!
----------------------------------------------------------
Please mark this thread as answer if you think it is helpful.
Thank you!
- Proposed as answer by Tao Liang Wednesday, February 4, 2009 10:26 AM
- Edited by Tao Liang Wednesday, February 4, 2009 10:37 AM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 1:57 PM
- Unmarked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:34 PM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 10:22 AM -
XAML:
<Window x:Class="_temple.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:_temple" Title="Window1" Height="400" Width="500"> <Grid> <local:MyToolBar x:Name="toolbar" Height="66" Margin="0,0,59,0" VerticalAlignment="Top" HorizontalAlignment="Stretch"> <local:MyToolBar.ItemTemplate> <DataTemplate> <Button Width="50" Height="20" Content="{Binding}" Click="ResetItemSource"/> </DataTemplate> </local:MyToolBar.ItemTemplate> </local:MyToolBar> </Grid> </Window>
- Proposed as answer by Tao Liang Wednesday, February 4, 2009 10:26 AM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 1:57 PM
- Unmarked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:35 PM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 10:23 AM -
C#:
using System; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Controls; using System.Collections; namespace _temple { public partial class Window1 : Window { ArrayList al = new ArrayList(); public Window1() { InitializeComponent(); for (int i = 0; i < 10;i ++ ) { al.Add( "Button"+i.ToString() ); } toolbar.ItemsSource = al; } private void ResetItemSource(object sender, RoutedEventArgs e) { Button btn = (Button)sender; string strBtn =btn.Content.ToString(); for (int i = 0; i < al.Count; i++) { string strI = al[i].ToString(); if (strI == strBtn) { al.RemoveAt(i); break; } } toolbar.ItemsSource = null; toolbar.ItemsSource = al; } } public class MyToolBar : ToolBar { private ToolBarPanel toolBarPanel; private ToolBarOverflowPanel toolBarOverflowPanel; protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) { if (toolBarPanel != null && toolBarOverflowPanel != null) { UpdateToolBarLayout(); } } } public override void OnApplyTemplate() { base.OnApplyTemplate(); toolBarPanel = base.GetTemplateChild("PART_ToolBarPanel") as ToolBarPanel; toolBarOverflowPanel = base.GetTemplateChild("PART_ToolBarOverflowPanel") as ToolBarOverflowPanel; } private void UpdateToolBarLayout() { toolBarPanel.LayoutUpdated += UpdateOverflowPanel; } public void UpdateOverflowPanel(object sender, EventArgs args) { toolBarOverflowPanel.InvalidateMeasure(); toolBarPanel.LayoutUpdated -= UpdateOverflowPanel; } } }
- Proposed as answer by Tao Liang Wednesday, February 4, 2009 10:26 AM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 1:57 PM
- Unmarked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:35 PM
- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 10:23 AM -
Nice, it work perfectly!
Thank you for all!
EDIT:
I remove the "if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)" statement because the bug appears when you delete some items in the OverflowPanel and if you add some items after!- Marked as answer by Chris_LaFouine Wednesday, February 4, 2009 2:59 PM
Wednesday, February 4, 2009 1:59 PM