如何使WPF组合框具有XAML中最宽元素的宽度?

Csu*_*enő 98 c# wpf combobox

我知道如何在代码中执行此操作,但这可以在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>
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Fre*_*lad 55

您无法在Xaml中直接执行此操作,但可以使用此附加行为.(宽度将在Designer中可见)

<ComboBox behaviors:ComboBoxWidthFromItemsBehavior.ComboBoxWidthFromItems="True">
    <ComboBoxItem Content="Short"/>
    <ComboBoxItem Content="Medium Long"/>
    <ComboBoxItem Content="Min"/>
</ComboBox>
Run Code Online (Sandbox Code Playgroud)

附加行为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);
    }
}
Run Code Online (Sandbox Code Playgroud)

它的作用是调用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这种扩展方法还提供了调用的能力

comboBox.SetWidthFromItems();
Run Code Online (Sandbox Code Playgroud)

在后面的代码中(例如在ComboBox.Loaded事件中)

  • 请注意,如果你在同一个窗口中有多个组合框(*它发生在我的窗口创建组合框并且其内容带有代码隐藏*),弹出窗口可以显示一秒钟.我想这是因为在调用任何"关闭弹出窗口"之前会发布多个"打开弹出窗口"消息.解决方案是使整个方法`SetWidthFromItems`使用action/delegate和具有空闲优先级的BeginInvoke异步(如在Loaded事件中所做的那样).这样,当信息泵不为空时,将不会进行任何措施,因此,不会发生消息交错 (7认同)

mic*_*tan 30

如果没有以下任何一项,则不能在XAML中:

  • 创建隐藏控件(Alan Hunford的答案)
  • 彻底改变ControlTemplate.即使在这种情况下,也可能需要创建ItemsPresenter的隐藏版本.

原因是我遇到的默认ComboBox ControlTemplates(Aero,Luna等)都将ItemsPresenter嵌套在Popup中.这意味着这些项目的布局将被推迟,直到它们实际可见.

一种简单的方法是修改默认的ControlTemplate,将最外层容器的MinWidth(它是Aero和Luna的Grid)绑定到PART_Popup的ActualWidth.当您单击下拉按钮时,您将能够让ComboBox自动同步它的宽度,而不是之前.

因此,除非您可以在布局系统中强制执行测量操作(可以通过添加第二个控件来执行),我认为不能这样做.

与往常一样,我对一个简短而优雅的解决方案持开放态度 - 但在这种情况下,代码隐藏或双控制/ ControlTemplate黑客攻击是我见过的唯一解决方案.


Alu*_*ord 10

是的,这个有点讨厌.

我过去所做的是在ControlTemplate中添加一个隐藏的列表框(其itemscontainerpanel设置为网格)同时显示每个项目,但其可见性设置为隐藏.

我很高兴听到任何更好的想法,不依赖于可怕的代码隐藏或你的观点必须理解它需要使用不同的控件来提供宽度来支持视觉效果(哎呀!).


Gas*_*ode 7

基于上面的其他答案,这是我的版本:

<Grid HorizontalAlignment="Left">
    <ItemsControl ItemsSource="{Binding EnumValues}" Height="0" Margin="15,0"/>
    <ComboBox ItemsSource="{Binding EnumValues}" />
</Grid>
Run Code Online (Sandbox Code Playgroud)

Horizo​​ntalAlignment ="Left"使用包含控件的整个宽度来停止控件.高度="0"隐藏项目控件.
Margin ="15,0"允许在组合框项目周围添加额外的铬(我不害怕Chrome不可知).

  • 这是迄今为止最简单的答案。必须这样做是荒谬的,但它比其他解决方法复杂和荒谬的数量级要少。为什么没有 SizeToOptions 属性,或者这不仅仅是控件的默认行为,超出了我的范围!WPF 有时完全是精神上的。不错,加斯波德! (4认同)