1

I'm using a combobox that gets it width from widest element described in Attached Behavior answer (currently 46 upvotes) in How can I make a WPF combo box have the width of its widest element in XAML?

public static void SetWidthFromItems(this ComboBox comboBox)
{
    double comboBoxWidth = 19;// comboBox.DesiredSize.Width;

    // Create the peer and provider to expand the comboBox in code behind. 
    ComboBoxAutomationPeer peer = new ComboBoxAutomationPeer(comboBox);
    IExpandCollapseProvider provider = (IExpandCollapseProvider)peer.GetPattern(PatternInterface.ExpandCollapse);
    EventHandler eventHandler = null;
    eventHandler = new EventHandler(delegate
    {
        if (comboBox.IsDropDownOpen &&
            comboBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        {
            double width = 0;
            foreach (var item in comboBox.Items)
            {
                 var container = comboBox.ItemContainerGenerator.ContainerFromItem(item);
                 if (container is ComboBoxItem)
                 {
                     var comboBoxItem = (ComboBoxItem) container;
                     comboBoxItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                     if (comboBoxItem.DesiredSize.Width > width)
                     {
                         width = comboBoxItem.DesiredSize.Width;
                     }
                 }
                 else
                 {
                    /* FIXME: coming here means that for some reason ComboBoxItems */ 
                    /* are not generated even if comboBox.ItemContainerGenerator.Status seems to be OK  */
                    return;
                 }                
            }
            comboBox.Width = comboBoxWidth + width;
            // Remove the event handler. 
            comboBox.ItemContainerGenerator.StatusChanged -= eventHandler;
            comboBox.DropDownOpened -= eventHandler;
            provider.Collapse();
        }
    });
    comboBox.ItemContainerGenerator.StatusChanged += eventHandler;
    comboBox.DropDownOpened += eventHandler;
    // Expand the comboBox to generate all its ComboBoxItem's. 
    provider.Expand();
}

However the solution does not work when scaling text size on the fly in win10. The problem is that even though the condition

comboBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated

is true, the call for

comboBox.ItemContainerGenerator.ContainerFromItem(item); 

returns null from seemingly ok item.

So my question is: How should I alter the code that width is calculated correctly? I'm asking this because I don't have win10 and can't reproduce and play around. I gotta ask a colleague to test it.

I tried removing line

comboBox.ItemContainerGenerator.StatusChanged -= eventHandler;

this resulted in the correct width being measured when the narrow combobox was clicked with mouse. So one answer would be to force StatusChanged event to be raised somewhere somehow.

char m
  • 7,840
  • 14
  • 68
  • 117

1 Answers1

0

I came up with a solution that is far from perfect being only a workaround. The measuring needs to be done only once cause i don't add items to ComboBox after it is ready. So I keep record of measured ComboBoxes:

    private static HashSet<string> _measuredWidthNamesSet = new HashSet<string>();
    private static void OnComboBoxLoaded(object sender, RoutedEventArgs e)
    {
        ComboBox comboBox = sender as ComboBox;
        if (!_measuredWidthNamesSet.Contains(comboBox.Name))
        {
            Action action = () => { comboBox.SetWidthFromItems(_measuredWidthNamesSet); };
            comboBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
        }
    }

If comboBox.ItemContainerGenerator.ContainerFromItem(item) returns null we don't se the width and don't add the ComboBox name to set of measured ComboBoxes:

    public static void SetWidthFromItems(this ComboBox comboBox, HashSet<string> measuredWidthNamesSet)
    {
        double comboBoxWidth = 19;// comboBox.DesiredSize.Width;

        // Create the peer and provider to expand the comboBox in code behind. 
        ComboBoxAutomationPeer peer = new ComboBoxAutomationPeer(comboBox);
        IExpandCollapseProvider provider = (IExpandCollapseProvider)peer.GetPattern(PatternInterface.ExpandCollapse);
        EventHandler eventHandler = null;
        eventHandler = new EventHandler(delegate
        {
            if (comboBox.IsDropDownOpen &&
                comboBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                bool isSuccess = true;
                double width = 0;
                foreach (var item in comboBox.Items)
                {
                    var container = comboBox.ItemContainerGenerator.ContainerFromItem(item);
                    if (container is ComboBoxItem)
                    {
                        var comboBoxItem = (ComboBoxItem) container;
                        comboBoxItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                        if (comboBoxItem.DesiredSize.Width > width)
                        {
                            width = comboBoxItem.DesiredSize.Width;
                        }
                    }
                    else
                    {
                        /* coming here means that for some reason ComboBoxItems are not generated even if
                         * comboBox.ItemContainerGenerator.Status seems to be OK */
                        isSuccess = false;
                        break;
                    }
                }
                if (isSuccess)
                {
                    comboBox.Width = comboBoxWidth + width;
                    measuredWidthNamesSet.Add(comboBox.Name);
                }

                // Remove the event handler. 
                comboBox.ItemContainerGenerator.StatusChanged -= eventHandler;
                comboBox.DropDownOpened -= eventHandler;
                provider.Collapse();
            }
        });
        comboBox.ItemContainerGenerator.StatusChanged += eventHandler;
        comboBox.DropDownOpened += eventHandler;
        // Expand the comboBox to generate all its ComboBoxItem's. 
        provider.Expand();
    }
char m
  • 7,840
  • 14
  • 68
  • 117