WPF 콤보 상자를 XAML에서 가장 넓은 요소의 너비로 만들려면 어떻게해야합니까?


103

코드에서 수행하는 방법을 알고 있지만 XAML에서 수행 할 수 있습니까?

Window1.xaml :

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

Window1.xaml.cs :

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;
        }
    }
}

stackoverflow.com/questions/826985/ 에서 유사한 줄에 대한 다른 게시물을 확인하십시오 . 질문에 대한 답변이 있으면 "답변"으로 표시하십시오.
Sudeep

이 방법을 코드에서도 시도했지만 Vista와 XP간에 측정 값이 다를 수 있음을 발견했습니다. Vista에서는 DesiredSize에 일반적으로 드롭 다운 화살표 크기가 포함되지만 XP에서는 너비에 드롭 다운 화살표가 포함되지 않는 경우가 많습니다. 이제 내 결과는 부모 창이 표시되기 전에 측정을 시도했기 때문일 수 있습니다. Measure 앞에 UpdateLayout ()을 추가하면 도움이 될 수 있지만 앱에서 다른 부작용이 발생할 수 있습니다. 공유 할 의향이 있다면 해결책을 찾고 싶습니다.
jschroedl

문제를 어떻게 해결 했습니까?
Andrew Kalashnikov

답변:


31

다음 중 하나가 없으면 XAML에있을 수 없습니다.

  • 숨겨진 컨트롤 만들기 (Alan Hunford의 답변)
  • ControlTemplate을 대폭 변경합니다. 이 경우에도 ItemsPresenter의 숨겨진 버전을 만들어야 할 수 있습니다.

그 이유는 내가 본 기본 ComboBox ControlTemplate (Aero, Luna 등)이 모두 ItemsPresenter를 Popup에 중첩하기 때문입니다. 즉, 이러한 항목의 레이아웃이 실제로 표시 될 때까지 연기됩니다.

이를 테스트하는 쉬운 방법은 가장 바깥 쪽 컨테이너의 MinWidth (Aero와 Luna 모두에 대한 Grid)를 PART_Popup의 ActualWidth에 바인딩하도록 기본 ControlTemplate을 수정하는 것입니다. 드롭 버튼을 클릭하면 ComboBox가 너비를 자동으로 동기화 할 수 있지만 이전에는 동기화 할 수 없습니다.

당신이 (당신이 레이아웃 시스템의 측정 작업을 강제하지 않는 그래서 수있는 제 2 제어를 추가 할을), 나는 그것을 할 수 있다고 생각하지 않습니다.

항상 그렇듯이 짧고 우아한 솔루션에 열려 있지만이 경우에는 코드 숨김 또는 이중 제어 / ControlTemplate 해킹이 내가 본 유일한 솔루션입니다.


57

Xaml에서 직접 수행 할 수는 없지만이 연결된 동작을 사용할 수 있습니다. (너비는 디자이너에 표시됩니다)

<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);
    }
}

그것이하는 일은 SetWidthFromItems라는 ComboBox에 대한 확장 메서드를 호출하는 것입니다.이 메서드는 (보이지 않게) 자체적으로 확장 및 축소 된 다음 생성 된 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, 훌륭한 솔루션! 나는 같은 라인을 따라 무언가를하려고했지만 결국 당신의 구현을 사용했습니다. (몇 가지 수정을 거쳐)
Thomas Levesque

1
놀라운 감사합니다. 수락 된 답변으로 표시되어야합니다. 연결된 속성과 같은 항상 : 모든 것에 대한 방법
이그나시오 솔러 가르시아

내가 생각하는 한 최고의 솔루션. 나는 인터넷에서 여러 가지 트릭을 시도했으며 귀하의 솔루션은 내가 찾은 가장 쉽고 쉬운 방법입니다. +1.
paercebal

7
동일한 창에 여러 콤보 상자가있는 경우 ( 콤보 상자와 코드 숨김을 사용하여 내용을 만드는 창에서 발생했습니다 ) 팝업이 잠시 동안 표시 될 수 있습니다. "close popup"이 호출되기 전에 여러 "open popup"메시지가 게시되기 때문이라고 생각합니다. 이에 대한 해결책 SetWidthFromItems은 작업 / 대리자 및 Idle 우선 순위를 가진 BeginInvoke를 사용하여 전체 메서드를 비동기 적 으로 만드는 것입니다 (Loaded 이벤트에서 수행됨). 이렇게하면 메시지 펌프가 비어 있지 않은 동안에는 조치가 수행되지 않으므로 메시지 인터리빙이 발생하지 않습니다.
paercebal

1
double comboBoxWidth = 19;당신의 코드에서 매직 넘버 는 SystemParameters.VerticalScrollBarWidth?
Jf Beaulac 2016

10

네, 이건 좀 더럽습니다.

과거에 내가 한 일은 모든 항목을 동시에 표시하지만 가시성을 숨김으로 설정 한 숨겨진 목록 상자 (itemcontainerpanel이 그리드로 설정 됨)를 ControlTemplate에 추가하는 것입니다.

끔찍한 코드 숨김에 의존하지 않는 더 나은 아이디어 나 시각 자료를 지원하기 위해 너비를 제공하기 위해 다른 컨트롤을 사용해야한다는 것을 이해해야하는 귀하의 관점에 대해 듣게되어 기쁩니다.


1
이 접근 방식은 가장 넓은 항목이 선택한 항목 일 때 완전히 보이도록 콤보의 너비를 충분히 조정합니까? 이것은 내가 문제를 본 곳입니다.
jschroedl

8

위의 다른 답변을 바탕으로 내 버전은 다음과 같습니다.

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

HorizontalAlignment = "Left"는 포함하는 컨트롤의 전체 너비를 사용하여 컨트롤을 중지합니다. Height = "0"은 항목 컨트롤을 숨 깁니다.
Margin = "15,0"은 콤보 상자 항목 주위에 추가 크롬을 허용합니다 (크롬 불가지론이 아닙니다).


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>

물론 Grid의 SharedSizeScope 기능과 유사한 크기로 함께 크기를 조정하려는 경우가 아니라면 각 콤보 상자에 대해 별도의 GrowConverter 인스턴스가 필요합니다.


1
훌륭하지만 가장 긴 항목을 선택한 후에 만 ​​"안정"됩니다.
primfaktor

1
옳은. 나는 WinForms에서 이것에 대해 뭔가를했는데, 여기서 텍스트 API를 사용하여 콤보 상자의 모든 문자열을 측정하고이를 고려하여 최소 너비를 설정했습니다. 특히 항목이 문자열이 아니거나 바인딩에서 오는 경우 WPF에서 동일한 작업을 수행하는 것이 훨씬 더 어렵습니다.
Cheetah

3

Maleak의 답변에 대한 후속 조치 : 그 구현이 너무 마음에 들었고 이에 대한 실제 동작을 작성했습니다. System.Windows.Interactivity를 참조 할 수 있도록 Blend SDK가 필요합니다.

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를 일시적으로 활성화 할 수도 없습니다.
FlyingFoX

1

보관 용 계정 뒤에 동일한 콘텐츠가 포함 된 목록 상자를 놓습니다. 그런 다음 다음과 같은 바인딩으로 올바른 높이를 적용하십시오.

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

1

제 경우에는 훨씬 더 간단한 방법이 트릭을 수행하는 것처럼 보였으며 콤보 상자를 래핑하기 위해 추가 stackPanel을 사용했습니다.

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

(Visual Studio 2008에서 작업)


1

상위 답변에 대한 대안 은 모든 항목을 측정하는 대신 팝업 자체 를 측정하는 것입니다. 약간 더 간단한 SetWidthFromItems()구현 제공 :

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;
    }
}

장애인 ComboBox도 마찬가지입니다.


0

나는 UpdateLayout()모두 UIElement가 가진 방법을 발견했을 때 스스로 답을 찾고 있었다 .

지금은 매우 간단합니다. 고맙게도!

그냥 전화를 ComboBox1.Updatelayout();설정 한 후 또는 수정 ItemSource.


0

실제로 Alun Harford의 접근 방식 :

<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>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.