How can I make a WPF combo box have the width of its widest element in XAML?

前端 未结 13 1981
清酒与你
清酒与你 2020-11-30 18:58

I know how to do it in code, but can this be done in XAML ?

Window1.xaml:



        
相关标签:
13条回答
  • 2020-11-30 19:20

    Alun Harford's approach, in practice :

    <Grid>
    
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
      </Grid.ColumnDefinitions>
    
      <!-- hidden listbox that has all the items in one grid -->
      <ListBox ItemsSource="{Binding Items, ElementName=uiComboBox, Mode=OneWay}" Height="10" VerticalAlignment="Top" Visibility="Hidden">
        <ListBox.ItemsPanel><ItemsPanelTemplate><Grid/></ItemsPanelTemplate></ListBox.ItemsPanel>
      </ListBox>
    
      <ComboBox VerticalAlignment="Top" SelectedIndex="0" x:Name="uiComboBox">
        <ComboBoxItem>foo</ComboBoxItem>
        <ComboBoxItem>bar</ComboBoxItem>
        <ComboBoxItem>fiuafiouhoiruhslkfhalsjfhalhflasdkf</ComboBoxItem>
      </ComboBox>
    
    </Grid>
    
    0 讨论(0)
  • 2020-11-30 19:22

    This keeps the width to the widest element but only after opening the combo box once.

    <ComboBox ItemsSource="{Binding ComboBoxItems}" Grid.IsSharedSizeScope="True" HorizontalAlignment="Left">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="sharedSizeGroup"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding}"/>
                </Grid>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>
    
    0 讨论(0)
  • 2020-11-30 19:24

    Put an listbox containing the same content behind the dropbox. Then enforce correct height with some binding like this:

    <Grid>
           <ListBox x:Name="listBox" Height="{Binding ElementName=dropBox, Path=DesiredSize.Height}" /> 
            <ComboBox x:Name="dropBox" />
    </Grid>
    
    0 讨论(0)
  • 2020-11-30 19:24

    I wanted it to only resize to the max element while the dropdown is open, and otherwise fit to the selected value. Here's the code for that:

    Based in part on Frederik's answer (which didn't actually work for me)

    public static class ComboBoxAutoWidthBehavior {
        public static readonly DependencyProperty ComboBoxAutoWidthProperty =
                DependencyProperty.RegisterAttached(
                    "ComboBoxAutoWidth",
                    typeof(bool),
                    typeof(ComboBoxAutoWidthBehavior),
                    new UIPropertyMetadata(false, OnComboBoxAutoWidthPropertyChanged)
                );
    
        public static bool GetComboBoxAutoWidth(DependencyObject obj) {
            return (bool) obj.GetValue(ComboBoxAutoWidthProperty);
        }
    
        public static void SetComboBoxAutoWidth(DependencyObject obj, bool value) {
            obj.SetValue(ComboBoxAutoWidthProperty, value);
        }
    
        private static void OnComboBoxAutoWidthPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e) {
            if(dpo is ComboBox comboBox) {
                if((bool) e.NewValue) {
                    comboBox.Loaded += OnComboBoxLoaded;
                    comboBox.DropDownOpened += OnComboBoxOpened;
                    comboBox.DropDownClosed += OnComboBoxClosed;
                } else {
                    comboBox.Loaded -= OnComboBoxLoaded;
                    comboBox.DropDownOpened -= OnComboBoxOpened;
                    comboBox.DropDownClosed -= OnComboBoxClosed;
                }
            }
        }
    
        private static void OnComboBoxLoaded(object sender, EventArgs eventArgs) {
            ComboBox comboBox = (ComboBox) sender;
            comboBox.SetMaxWidthFromItems();
        }
    
        private static void OnComboBoxOpened(object sender, EventArgs eventArgs) {
            ComboBox comboBox = (ComboBox) sender;
            comboBox.Width = comboBox.MaxWidth;
        }
    
        private static void OnComboBoxClosed(object sender, EventArgs eventArgs) => ((ComboBox) sender).Width = double.NaN;
    }
    
    public static class ComboBoxExtensionMethods {
        public static void SetMaxWidthFromItems(this ComboBox combo) {
            double idealWidth = combo.MinWidth;
            string longestItem = combo.Items.Cast<object>().Select(x => x.ToString()).Max(x => (x?.Length, x)).x;
            if(longestItem != null && longestItem.Length >= 0) {
                string tmpTxt = combo.Text;
                combo.Text = longestItem;
                Thickness tmpMarg = combo.Margin;
                combo.Margin = new Thickness(0);
                combo.UpdateLayout();
    
                combo.Width = double.NaN;
                combo.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
    
                idealWidth = Math.Max(idealWidth, combo.DesiredSize.Width);
    
                combo.Text = tmpTxt;
                combo.Margin = tmpMarg;
            }
    
            combo.MaxWidth = idealWidth;
        }
    }
    

    And you enable it like this:

    <ComboBox behaviours:ComboBoxAutoWidthBehavior.ComboBoxAutoWidth="True" />
    

    You could also just set Width directly instead of MaxWidth, and then remove the DropDownOpened and Closed parts if you want it to behave like the other anwsers.

    0 讨论(0)
  • 2020-11-30 19:25

    An alternative solution to the top answer is to Measure the Popup itself rather than measuring all the items. Giving slightly simpler SetWidthFromItems() implementation:

    private static void SetWidthFromItems(this ComboBox comboBox)
    {
        if (comboBox.Template.FindName("PART_Popup", comboBox) is Popup popup 
            && popup.Child is FrameworkElement popupContent)
        {
            popupContent.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            // suggested in comments, original answer has a static value 19.0
            var emptySize = SystemParameters.VerticalScrollBarWidth + comboBox.Padding.Left + comboBox.Padding.Right;
            comboBox.Width = emptySize + popupContent.DesiredSize.Width;
        }
    }
    

    works on disabled ComboBoxes as well.

    0 讨论(0)
  • 2020-11-30 19:28

    Based on the other answers above, here's my version:

    <Grid HorizontalAlignment="Left">
        <ItemsControl ItemsSource="{Binding EnumValues}" Height="0" Margin="15,0"/>
        <ComboBox ItemsSource="{Binding EnumValues}" />
    </Grid>
    

    HorizontalAlignment="Left" stops the controls using the full width of the containing control. Height="0" hides the items control.
    Margin="15,0" allows for additional chrome around combo-box items (not chrome agnostic I'm afraid).

    0 讨论(0)
提交回复
热议问题