programing

WPF 콤보 박스에서 XAML의 가장 넓은 요소의 폭을 설정하려면 어떻게 해야 합니까?

oldcodes 2023. 4. 19. 23:28
반응형

WPF 콤보 박스에서 XAML의 가장 넓은 요소의 폭을 설정하려면 어떻게 해야 합니까?

코드로 하는 방법은 알고 있습니다만, XAML로 할 수 있습니까?

Window 1.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;
        }
    }
}

Xaml에서는 직접 할 수 없지만 이 Attached Behavior를 사용할 수 있습니다.(폭은 설계자에 표시됩니다)

<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에 따라 폭을 계산합니다.(IEexpandCollapseProvider는 UIAutomationProvider에 대한 참조가 필요합니다.)

다음으로 확장 메서드 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)에 있습니다.로드된 이벤트)

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

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

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

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

따라서 레이아웃 시스템에서 Measure 조작을 강제할 수 없는 한(두 번째 컨트롤을 추가하여 할 있음) 것은 불가능하다고 생각합니다.

항상 그렇듯이 짧고 우아한 솔루션에 대해 개방적입니다.그러나 이 경우 코드 이면 또는 듀얼 컨트롤/ControlTemplate 해커가 제가 본 유일한 솔루션입니다.

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

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

HorizontalAlignment="Left"를 선택합니다.=="0"으로 하다
마진="15,0"은 콤보 박스 아이템 주위에 크롬을 추가할 수 있습니다(크롬에 구애받지 않습니다).

네, 이건 좀 심하네요.

지금까지 ControlTemplate에 숨겨진 목록 상자(항목 컨테이너 패널이 그리드로 설정됨)를 추가하여 모든 항목을 동시에 표시하지만 표시는 숨김으로 설정했습니다.

끔찍한 코드 배후에 의존하지 않는 더 나은 아이디어나 비주얼을 지원하는 폭을 제공하기 위해 다른 컨트롤을 사용해야 한다는 것을 이해해야 하는 귀하의 견해를 듣고 싶습니다(우엑!).

저는 이 문제에 대한 "충분히 좋은" 해결책을 찾았습니다. 이전 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>

물론 그리드의 SharedSizeScope 기능과 마찬가지로 콤보 상자마다 개별 GrowConverter 인스턴스가 필요합니다.

말락의 답변에 대한 후속 조치:저는 그 구현이 너무 마음에 들어서 실제 행동을 썼습니다.물론 시스템을 참조할 수 있도록 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();
        }
    }
}

저 같은 경우에는 훨씬 더 간단한 방법으로 콤보박스를 포장하기 위해 여분의 스택패널을 사용했습니다.

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

(visual studio 2008에서 공개)

상위 답변에 대한 다른 해결책은 모든 항목을 측정하는 것이 아니라 팝업 자체를 측정하는 것입니다.조금 심플하게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★★★★★★★★★★★★★★★★★★.

드롭 박스 뒤에 같은 내용을 포함한 리스트 박스를 넣습니다.그런 다음 다음과 같은 바인딩으로 올바른 높이를 적용합니다.

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

실제로 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>

드롭다운이 열려 있는 동안 최대 요소에만 크기를 조정하고, 그렇지 않으면 선택한 값에 맞게 크기를 조정하려고 했습니다.그 코드는 다음과 같습니다.

Frederik의 답변(실제로 나에게는 효과가 없었다)에 근거하고 있다.

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

다음과 같이 활성화합니다.

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

MaxWidth 대신 Width를 직접 설정한 다음 다른 Anwers와 같이 작동하려면 DropDownOpened 및 Closed 부분을 제거할 수도 있습니다.

직접 때 그 답을 .UpdateLayout()모든 방법UIElement가지다.

지금은 매우 간단합니다.다행히!

그냥 전화하세요.ComboBox1.Updatelayout();설정 또는 변경 후ItemSource.

이렇게 하면 콤보 상자를 한 번 연 후에만 폭이 가장 넓은 요소로 유지됩니다.

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

언급URL : https://stackoverflow.com/questions/1034505/how-can-i-make-a-wpf-combo-box-have-the-width-of-its-widest-element-in-xaml

반응형