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では、幅にドロップダウン矢印が含まれないことがよくあります。今、私の結果は、親ウィンドウが表示される前に測定を試みているためかもしれません。メジャーの前にUpdateLayout()を追加すると役立ちますが、アプリで他の副作用が発生する可能性があります。共有したいのであれば、あなたが思いついた解決策を見てみたいと思います。
jschroedl 2009

どのように問題を解決しましたか?
アンドリューカラシニコフ

回答:


31

これは、次のいずれかがないとXAMLに含めることはできません。

  • 隠しコントロールを作成する(Alan Hunfordの答え)
  • ControlTemplateを大幅に変更します。この場合でも、ItemsPresenterの非表示バージョンを作成する必要がある場合があります。

この理由は、私が遭遇したデフォルトのComboBox ControlTemplates(Aero、Lunaなど)はすべて、PopupでItemsPresenterをネストしているためです。つまり、これらのアイテムのレイアウトは、実際に表示されるまで延期されます。

これをテストする簡単な方法は、デフォルトのControlTemplateを変更して、最も外側のコンテナー(AeroとLunaの両方のグリッド)のMinWidthをPART_PopupのActualWidthにバインドすることです。ドロップボタンをクリックしたときにComboBoxの幅を自動的に同期させることができますが、以前は同期できません。

したがって、レイアウトシステムでメジャー操作を強制できない限り(2番目のコントロールを追加することで実行できます)、それができるとは思いません。

いつものように、私は短くてエレガントなソリューションを受け入れます-この場合、コードビハインドまたはデュアルコントロール/ ControlTemplateハックが私が見た唯一のソリューションです。


57

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

それが行うことは、それ自体が(見えないように)展開および縮小し、生成されたComboBoxItemsに基づいて幅を計算するSetWidthFromItemsというComboBoxの拡張メソッドを呼び出すことです。(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 '17年

1
本当にありがとう。これは、受け入れられた回答としてマークする必要があります。添付プロパティは常にすべてのものへの道であるように見えます:)
Ignacio Soler Garcia

私に関する限り、最善の解決策です。私はインターネット全体から複数のトリックを試しましたが、あなたの解決策は私が見つけた中で最も簡単なものです。+1。
paercebal 2012年

7
同じウィンドウに複数のコンボボックスがある場合(コンボボックスとそのコンテンツを分離コードで作成するウィンドウで発生した場合)、ポップアップが一瞬表示される可能性があることに注意してください。これは、「ポップアップを閉じる」が呼び出される前に、「ポップアップを開く」という複数のメッセージが投稿されるためだと思います。その解決策は、SetWidthFromItems(Loadedイベントで行われたように)アクション/デリゲートとアイドル優先度のBeginInvokeを使用して、メソッド全体を非同期にすることです。このように、メッセージポンプが空でない間は対策が行われないため、メッセージのインターリーブは発生しません
paercebal

1
マジックナンバーdouble comboBoxWidth = 19;はあなたのコードに関連していSystemParameters.VerticalScrollBarWidthますか?
Jf Beaulac 2016年

10

ええ、これは少し厄介です。

過去に私がやったことは、ControlTemplateに非表示のリストボックス(itemscontainerpanelをグリッドに設定)を追加して、すべての項目を同時に表示し、表示を非表示に設定することです。

恐ろしいコードビハインドに依存しないより良いアイデアや、ビジュアルをサポートする幅を提供するために別のコントロールを使用する必要があることを理解する必要があることを聞いて私は嬉しく思います(うん!)


1
このアプローチは、選択されたアイテムであるときに最も広いアイテムが完全に表示されるように、コンボのサイズを十分に大きくしますか?ここで問題が発生しました。
jschroedl 2009

8

上記の他の回答に基づいて、これが私のバージョンです:

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

Horizo​​ntalAlignment = "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>

もちろん、グリッドのSharedSizeScope機能と同様に、それらのセットのサイズを一緒に設定したくない場合を除き、コンボボックスごとにGrowConverterの個別のインスタンスが必要です。


1
いいですが、最も長いエントリを選択した後でのみ「安定」しています。
primfaktor 2012年

1
正しい。WinFormsでこれについて何かを行いました。テキストAPIを使用してコンボボックス内のすべての文字列を測定し、それを考慮して最小幅を設定しました。WPFで同じことを行うのは、特にアイテムが文字列ではない場合やバインディングからのものである場合には、かなり困難です。
チーター、

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

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

無効なComboBoxesでも動作します。


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

これにより、コンボボックスを1回開いた後でのみ、幅が最も広い要素に維持されます。

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