views:

54

answers:

1

I have a ItemsControl in a ScrollViewer, and when the items exceed the width of the ScrollViewer they are put into a ContextMenu and shown as a DropDown instead. My problem is that when the Context Menu is first loaded, it saves the saves the size of the Menu and does not redraw when more commands get added/removed.

For example, a panel has 3 commands. 1 is visible and 2 are in the Menu. Viewing the menu shows the 2 commands and draws the control, but then if you resize the panel so 2 are visible and only 1 command is in the menu, it doesn't redraw the menu to eliminate that second menu item. Or even worse, if you shrink the panel so that no commands are shown and all 3 are in the Menu, it will only show the top 2.

http://i193.photobucket.com/albums/z197/Lady53461/ContextMenuRedraw.jpg

Here's my code:

<Button Click="DropDownMenu_Click"
        ContextMenuOpening="DropDownMenu_ContextMenuOpening">

    <Button.ContextMenu>
        <ContextMenu ItemsSource="{Binding Path=MenuCommands}" Placement="Bottom">
            <ContextMenu.Resources>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Command" Value="{Binding Path=Command}" />
                    <Setter Property="Visibility" Value="{Binding Path=IsVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}}"/>
                </Style>
            </ContextMenu.Resources>
            <ContextMenu.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=DisplayName}" />
                </DataTemplate>
            </ContextMenu.ItemTemplate>
        </ContextMenu>
    </Button.ContextMenu>
</Button>

Code Behind:

        void DropDownMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
    {
        Button b = sender as Button;
        b.ContextMenu.IsOpen = false;
        e.Handled = true;
    }

    private void DropDownMenu_Click(object sender, RoutedEventArgs e)
    {
        Button b = sender as Button;

        ContextMenu cMenu = b.ContextMenu;
        if (cMenu != null)
        {
            cMenu.PlacementTarget = b;
            cMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
            cMenu.IsOpen = true;
        }
    }

I have tried using InvalidateVisual and passing an empty delegate on Render to try and force a redraw, however neither works. I'm using .Net 4.0.

+1  A: 

Hi,

Is MenuCommands a collection? If it is, is it an ObservableCollection?

If you bind a collection to an ItemsControl, that collection must implement INotifyCollectionChanged interface to let the ItemsControl know that the number of items in the collection has changed, so that the control can "redraw" itself.

decyclone
It is an ObservableCollection of RelayCommands
Rachel
Ok then, but I don't see any code that changes the MenuCommands collection! What part of code changes it?
decyclone
The ScrollViewer.SizeChanged event modifies the MenuCommands collection and changes the IsVisible property based on if the object is within the ScrollViewers viewport or not... hrrm perhaps modifying an item in an ObservableCollection doesn't trigger the ObservableCollection changed event?
Rachel
That is right! Why don't you just reset the collection and add only the items that need to be displayed in the menu every time the size changes of the scroll viewer or something like that?
decyclone
I use the collection for two controls- The ScrollViewer to show visible commands and the ContextMenu for hidden ones. I didn't want to create two collections. Do you know how I can force it to rebind to the target? I tried using cMenu.BindingExpression(MenuItem.ItemsSourceProperty).UpdateTarget()however it did not change anything.
Rachel
How about setting ItemsSource property to null and then again assign it with the collection? Or create a custom collection that inherits from ObservableCollection, expose a public method in it which raises CollectionChanged event, and call that method to update the binding.
decyclone
Thank you! Setting it to null and back again did correctly update the visual. The ObservableCollection is actually a custom class already that inherits from ObservableCollection (I wanted sorting) so I will add a method to it which updates the binding.
Rachel