RunningCSharp

MS系開発者による、雑多な記事。記事は所属企業とは関係のない、個人の見解です。

WPF:内容に合わせて表示幅の変わるコンボボックス

xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ComboBox Name="combobox" HorizontalAlignment="Center" VerticalAlignment="Center">
            <ComboBoxItem>aaaa</ComboBoxItem>
            <ComboBoxItem>aaaa</ComboBoxItem>
            <ComboBoxItem>aaaa</ComboBoxItem>
            <ComboBoxItem>aaaazzzzzzzzzzzzzz</ComboBoxItem>
            <ComboBoxItem>aaaa</ComboBoxItem>
        </ComboBox>
    </Grid>
</Window>

コードビハインド

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            combobox.Loaded += Combobox_Loaded;
            combobox.Unloaded += Combobox_Unloaded;
        }

        private void Combobox_Loaded(object sender, RoutedEventArgs e)
        {
            ComboBox combo = sender as ComboBox;
            App.Current.Dispatcher.BeginInvoke(new Action(() => SetWidth(combo)));
        }

        private void Combobox_Unloaded(object sender, RoutedEventArgs e)
        {
            ComboBox comboBox = sender as ComboBox;
            if (comboBox == null) return;

            combobox.Loaded -= Combobox_Loaded;
            combobox.Unloaded -= Combobox_Unloaded; 
        }

        public static void SetWidth(ComboBox combo)
        {
            double itemWidth = 0;
            foreach (var item in combo.Items)
            {
                ComboBoxItem comboboxItem = item as ComboBoxItem;
                if (comboboxItem == null) continue;
                //コンテンツの対象文字列を取得
                //MVVMでItemsSourceにバインドしている場合、Itemsの内容がComboBoxItemではない場合があるので
                //その場合は表示文字列を取得できるよう修正してください
                string text = comboboxItem.Content.ToString();
                var size = string.IsNullOrEmpty(text) ? new Size(0d, 0d) : Measure(text, combo);
                if (size.Width > itemWidth)
                {
                    itemWidth = size.Width;
                }
            }
            var button = FindVisualChild<ToggleButton>(combo);
            if (button != null)
            {
                combo.Width = itemWidth + button.ActualWidth;
            }
        }

        private static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);

                if (child != null && child is T)
                    return (T)child;

                var childitem = FindVisualChild<T>(child);
                if (childitem != null)
                {
                    return childitem;
                }
            }
            return null;
        }

        private static Size Measure(string text, Control control)
        {
            var formattedText = new FormattedText(
                text,
                CultureInfo.CurrentUICulture,
                FlowDirection.LeftToRight,
                new Typeface(control.FontFamily, control.FontStyle, control.FontWeight, control.FontStretch),
                control.FontSize,
                Brushes.Black);

            return new Size(formattedText.Width, formattedText.Height);
        }
    }
}

上記コードで、アイテムの文字の長さに合わせてWidthが変わるコンボボックスとなる。