Вопрос по combobox, wpf, c# – Как сделать так, чтобы в поле со списком WPF была ширина самого широкого элемента в XAML?

88

Я знаю, как это сделать в коде, но можно ли это сделать в XAML?

Window1.xaml:

<code><Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <ComboBox Name="ComboBox1" HorizontalAlignment="Left" VerticalAlignment="Top">
            <ComboBoxItem>ComboBoxItem1</ComboBoxItem>
            <ComboBoxItem>ComboBoxItem2</ComboBoxItem>
        </ComboBox>
    </Grid>
</Window>
</code>

Window1.xaml.cs:

<code>using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            double width = 0;
            foreach (ComboBoxItem item in ComboBox1.Items)
            {
                item.Measure(new Size(
                    double.PositiveInfinity, double.PositiveInfinity));
                if (item.DesiredSize.Width > width)
                    width = item.DesiredSize.Width;
            }
            ComboBox1.Measure(new Size(
                double.PositiveInfinity, double.PositiveInfinity));
            ComboBox1.Width = ComboBox1.DesiredSize.Width + width;
        }
    }
}
</code>
Как вы решили свою проблему? Andrew Kalashnikov
Я попробовал этот подход и в коде, но обнаружил, что измерение может варьироваться в зависимости от Vista и XP. В Vista DesiredSize обычно включает размер стрелки раскрывающегося списка, но в XP часто ширина не включает стрелку раскрывающегося списка. Теперь мои результаты могут быть из-за того, что я пытаюсь выполнить измерение до того, как родительское окно станет видимым. Добавление UpdateLayout () до измерения может помочь, но может вызвать другие побочные эффекты в приложении. Мне было бы интересно увидеть решение, которое вы придумали, если хотите поделиться. jschroedl
Проверьте другой пост по аналогичным линиям наstackoverflow.com/questions/826985/… Пожалуйста, отметьте ваш вопрос как & quot; отвеченный & quot; если это ответит на ваш вопрос. Sudeep

Ваш Ответ

12   ответов
7

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

HorizontalAlignment = & Quot; левый & Quot; останавливает элементы управления, используя всю ширину содержащего элемента управления. Высота = & Quot; 0 & Quot; скрывает элементы управления
Маржа = & Quot; & Quot 15,0; допускает добавление хрома вокруг элементов комбинированного списка (не боюсь, что я не боюсь хрома).

10

То, что я делал в прошлом, - это добавление в ControlTemplate скрытого списка (с его элементом itemscontainer, установленным в сетку), показывающего каждый элемент одновременно, но с их видимостью, скрытой.

Я был бы рад услышать о каких-либо лучших идеях, которые не основаны на ужасном коде позади, или, по вашему мнению, необходимо понимать, что ему нужно использовать другой элемент управления для обеспечения ширины для поддержки визуальных эффектов (фу!).

Будет ли этот подход изменять размер комбо достаточно широко, чтобы самый широкий элемент был полностью виден, когда он выбранный элемент? Здесь я видел проблемы.
Не могли бы вы показать свой код.
1

Я просто использовал дополнительную стековую панель, чтобы обернуть комбинированный список.

<StackPanel Grid.Row="1" Orientation="Horizontal">
    <ComboBox ItemsSource="{Binding ExecutionTimesModeList}" Width="Auto"
        SelectedValuePath="Item" DisplayMemberPath="FriendlyName"
        SelectedValue="{Binding Model.SelectedExecutionTimesMode}" />    
</StackPanel>

(работал в визуальной студии 2008)

-1: Я попробовал это, и это не работает вообще.
1

содержащий тот же контент, позади выпадающего списка. Затем установите правильную высоту с помощью некоторой привязки, например:

<Grid>
       <ListBox x:Name="listBox" Height="{Binding ElementName=dropBox, Path=DesiredSize.Height}" /> 
        <ComboBox x:Name="dropBox" />
</Grid>
0

<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

но только после однократного открытия поля со списком.

<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>
29

Creating a hidden control (Alan Hunford's answer) Changing the ControlTemplate drastically. Even in this case, a hidden version of an ItemsPresenter may need to be created.

Причина этого заключается в том, что стандартные шаблоны управления ComboBox, с которыми я сталкиваюсь (Aero, Luna и т. Д.), Все вкладывают ItemsPresenter во всплывающее окно. Это означает, что расположение этих элементов откладывается до тех пор, пока они не станут видимыми.

Простой способ проверить это состоит в том, чтобы изменить шаблон ControlTemplate по умолчанию, чтобы связать MinWidth самого внешнего контейнера (это Grid для Aero и Luna) с ActualWidth PART_Popup. Вы сможете автоматически синхронизировать ComboBox с шириной, когда вы нажимаете кнопку перетаскивания, но не раньше.

Так что, если вы не можете принудительно выполнить операцию измерения в системе макета (которую выcan добавив второй элемент управления), я не думаю, что это можно сделать.

Как всегда, я открыт для короткого, элегантного решения, но в этом случае взломы code-behind или двойной контроль / ControlTemplate являются единственными решениями, которые я видел.

3

эта реализация мне очень понравилась, я написал для нее реальное поведение. Очевидно, что вам понадобится Blend SDK, чтобы вы могли ссылаться на System.Windows.Interactivity.

XAML:

    <ComboBox ItemsSource="{Binding ListOfStuff}">
        <i:Interaction.Behaviors>
            <local:ComboBoxWidthBehavior />
        </i:Interaction.Behaviors>
    </ComboBox>

Код:

using System;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;

namespace MyLibrary
{
    public class ComboBoxWidthBehavior : Behavior<ComboBox>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += OnLoaded;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.Loaded -= OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var desiredWidth = AssociatedObject.DesiredSize.Width;

            // Create the peer and provider to expand the comboBox in code behind. 
            var peer = new ComboBoxAutomationPeer(AssociatedObject);
            var provider = peer.GetPattern(PatternInterface.ExpandCollapse) as IExpandCollapseProvider;
            if (provider == null)
                return;

            EventHandler[] handler = {null};    // array usage prevents access to modified closure
            handler[0] = new EventHandler(delegate
            {
                if (!AssociatedObject.IsDropDownOpen || AssociatedObject.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
                    return;

                double largestWidth = 0;
                foreach (var item in AssociatedObject.Items)
                {
                    var comboBoxItem = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
                    if (comboBoxItem == null)
                        continue;

                    comboBoxItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                    if (comboBoxItem.DesiredSize.Width > largestWidth)
                        largestWidth = comboBoxItem.DesiredSize.Width;
                }

                AssociatedObject.Width = desiredWidth + largestWidth;

                // Remove the event handler.
                AssociatedObject.ItemContainerGenerator.StatusChanged -= handler[0];
                AssociatedObject.DropDownOpened -= handler[0];
                provider.Collapse();
            });

            AssociatedObject.ItemContainerGenerator.StatusChanged += handler[0];
            AssociatedObject.DropDownOpened += handler[0];

            // Expand the comboBox to generate all its ComboBoxItem's. 
            provider.Expand();
        }
    }
}
Это не работает, когда ComboBox не включен.provider.Expand() бросаетElementNotEnabledException, Когда ComboBox не включен, из-за того, что родительский элемент отключен, невозможно даже временно включить ComboBox, пока измерение не закончится.
4

том, чтобы заставить поле со списком никогда не уменьшаться ниже самого большого размера, который он имел, подобно старому WinForms AutoSizeMode = GrowOnly.

Я сделал это с помощью пользовательского конвертера значений:

public class GrowConverter : IValueConverter
{
    public double Minimum
    {
        get;
        set;
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var dvalue = (double)value;
        if (dvalue > Minimum)
            Minimum = dvalue;
        else if (dvalue < Minimum)
            dvalue = Minimum;
        return dvalue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Затем я настраиваю поле со списком в XAML следующим образом:

 <Whatever>
        <Whatever.Resources>
            <my:GrowConverter x:Key="grow" />
        </Whatever.Resources>
        ...
        <ComboBox MinWidth="{Binding ActualWidth,RelativeSource={RelativeSource Self},Converter={StaticResource grow}}" />
    </Whatever>

Обратите внимание, что при этом вам необходим отдельный экземпляр GrowConverter для каждого поля со списком, если, конечно, вы не хотите, чтобы их наборы объединялись в размер, аналогично функции Grid SharedSizeScope.

Хороший, но только & # x201C; стабильный & # x201D; после выбора самой длинной записи.
Правильный. Я сделал что-то об этом в WinForms, где я использовал текстовые API для измерения всех строк в поле со списком и устанавливал минимальную ширину, чтобы учесть это. Делать то же самое в WPF значительно сложнее, особенно когда ваши элементы не являются строками и / или являются обязательными.
54

но можете использовать это «Присоединенное поведение». (Ширина будет видна в дизайнере)

<ComboBox behaviors:ComboBoxWidthFromItemsBehavior.ComboBoxWidthFromItems="True">
    <ComboBoxItem Content="Short"/>
    <ComboBoxItem Content="Medium Long"/>
    <ComboBoxItem Content="Min"/>
</ComboBox>

Прикрепленное поведение ComboBoxWidthFromItemsProperty

public static class ComboBoxWidthFromItemsBehavior
{
    public static readonly DependencyProperty ComboBoxWidthFromItemsProperty =
        DependencyProperty.RegisterAttached
        (
            "ComboBoxWidthFromItems",
            typeof(bool),
            typeof(ComboBoxWidthFromItemsBehavior),
            new UIPropertyMetadata(false, OnComboBoxWidthFromItemsPropertyChanged)
        );
    public static bool GetComboBoxWidthFromItems(DependencyObject obj)
    {
        return (bool)obj.GetValue(ComboBoxWidthFromItemsProperty);
    }
    public static void SetComboBoxWidthFromItems(DependencyObject obj, bool value)
    {
        obj.SetValue(ComboBoxWidthFromItemsProperty, value);
    }
    private static void OnComboBoxWidthFromItemsPropertyChanged(DependencyObject dpo,
                                                                DependencyPropertyChangedEventArgs e)
    {
        ComboBox comboBox = dpo as ComboBox;
        if (comboBox != null)
        {
            if ((bool)e.NewValue == true)
            {
                comboBox.Loaded += OnComboBoxLoaded;
            }
            else
            {
                comboBox.Loaded -= OnComboBoxLoaded;
            }
        }
    }
    private static void OnComboBoxLoaded(object sender, RoutedEventArgs e)
    {
        ComboBox comboBox = sender as ComboBox;
        Action action = () => { comboBox.SetWidthFromItems(); };
        comboBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
    }
}

Что он делает, так это то, что он вызывает метод расширения для ComboBox, называемый SetWidthFromItems, который (незримо) сам разворачивается и сворачивается, а затем вычисляет ширину на основе сгенерированных ComboBoxItems. (IExpandCollapseProvider требует ссылки на UIAutomationProvider.dll)

Тогда метод расширения SetWidthFromItems

public static class ComboBoxExtensionMethods
{
    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)
                {
                    ComboBoxItem comboBoxItem = comboBox.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
                    comboBoxItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                    if (comboBoxItem.DesiredSize.Width > width)
                    {
                        width = comboBoxItem.DesiredSize.Width;
                    }
                }
                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();
    }
}

Этот метод расширения также предоставляет возможность вызова

comboBox.SetWidthFromItems();

в коде позади (например, в событии ComboBox.Loaded)

+1, отличное решение! Я пытался сделать что-то в том же духе, но в итоге я использовал вашу реализацию (с некоторыми изменениями)
Хорошее решение, спасибо.
Это магическое число:double comboBoxWidth = 19; в вашем коде, связанном сSystemParameters.VerticalScrollBarWidth?
Удивительно, спасибо. Это должно быть помечено как принятый ответ. Похоже, прикрепленные свойства - это всегда путь ко всему :)
Обратите внимание, что если у вас есть несколько комбинированных списков в одном окне (it happened for me with a window creating the comboboxes and their content with code-behind), всплывающие окна могут стать видимыми на секунду. Я полагаю, это потому, что множественное «открытое всплывающее окно» сообщения публикуются перед любым & quot; закрытием всплывающего окна & quot; называется. Решение для этого состоит в том, чтобы сделать весь методSetWidthFromItems асинхронное использование с использованием action / делегата и BeginInvoke с приоритетом Idle (как сделано в событии Loaded). Таким образом, никакие меры не будут выполняться, пока насос сообщений не пуст, и, следовательно, чередование сообщений не произойдет.
0

UpdateLayout() метод, который каждыйUIElement есть.

К счастью, теперь все очень просто!

Просто позвониComboBox1.Updatelayout(); после того, как вы установите или изменитеItemSource.

-1

<ComboBox Name="ComboBox1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100">

Похожие вопросы