Comment puis-je créer une boîte à options WPF ayant la largeur de son élément le plus large dans XAML?

Je sais comment le faire en code, mais cela peut-il être fait en XAML?

Window1.xaml:

   ComboBoxItem1 ComboBoxItem2    

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

Cela ne peut pas être en XAML sans:

  • Créer un contrôle caché (réponse d’Alan Hunford)
  • Changer le ControlTemplate de manière drastique. Même dans ce cas, une version cachée d’un ItemsPresenter peut devoir être créée.

La raison en est que les ComboBox ControlTemplates par défaut que j’ai rencontrés (Aero, Luna, etc.) imbriquent tous ItemsPresenter dans un Popup. Cela signifie que la disposition de ces éléments est différée jusqu’à ce qu’ils soient réellement visibles.

Un moyen facile de tester cela est de modifier le ControlTemplate par défaut pour lier la MinWidth du conteneur le plus externe (il s’agit d’une grid pour Aero et Luna) à ActualWidth de PART_Popup. Vous pourrez faire en sorte que ComboBox synchronise automatiquement sa largeur lorsque vous cliquez sur le bouton de repository, mais pas avant.

Donc, à moins que vous ne puissiez forcer une opération Measure dans le système de mise en page (ce que vous pouvez faire en ajoutant un second contrôle), je ne pense pas que cela puisse être fait.

Comme toujours, je suis ouvert à une solution courte et élégante, mais dans ce cas-ci, les seules solutions que j’ai vues sont les hacks de code-behind ou dual-control / ControlTemplate.

Vous ne pouvez pas le faire directement dans Xaml, mais vous pouvez utiliser ce comportement attaché. (La largeur sera visible dans le concepteur)

      

Le comportement associé 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); } } 

Ce qu’il fait, c’est qu’il appelle une méthode d’extension pour ComboBox appelée SetWidthFromItems qui (invisiblement) se développe et se réduit, puis calcule la largeur en fonction des ComboBoxItems générés. (IExpandCollapseProvider nécessite une référence à UIAutomationProvider.dll)

Puis la méthode d’extension 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(); } } 

Cette méthode d’extension permet également d’appeler

 comboBox.SetWidthFromItems(); 

dans le code derrière (par exemple dans l’événement ComboBox.Loaded)

Oui, celui-ci est un peu méchant.

Ce que j’ai fait dans le passé est d’append dans la ControlTemplate une liste cachée (dont le panel d’éléments est défini sur une grid) affichant chaque élément en même temps, mais dont la visibilité est masquée.

Je serais ravi d’entendre de meilleures idées qui ne reposent pas sur un code de retour horrible ou sur votre vue devant comprendre qu’il doit utiliser un contrôle différent pour fournir la largeur nécessaire à la prise en charge des éléments visuels (beurk!).

Sur la base des autres réponses ci-dessus, voici ma version:

     

HorizontalAlignment = “Left” arrête les contrôles en utilisant toute la largeur du contrôle contenant. Height = “0” masque le contrôle des éléments.
Margin = “15,0” permet d’append du chrome autour des éléments de la liste déroulante (pas de problème pour le chrome, j’ai peur).

Je me suis retrouvé avec une solution “assez bonne” à ce problème, à savoir que la zone de liste déroulante ne soit jamais inférieure à la plus grande taille, similaire à l’ancienne WinForms AutoSizeMode = GrowOnly.

La façon dont je l’ai fait était avec un convertisseur de valeur personnalisé:

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

Ensuite, je configure la boîte combinée dans XAML comme suit:

      ...   

Notez que pour cela, vous avez besoin d'une instance distincte de GrowConverter pour chaque combo, à moins bien sûr que vous souhaitiez en définir un ensemble, similaire à la fonctionnalité SharedSizeScope de la grid.

Une suite à la réponse de Maleak: J’ai tellement aimé cette implémentation que j’ai écrit un véritable Comportement. De toute évidence, vous aurez besoin du SDK Blend pour pouvoir référencer System.Windows.Interactivity.

XAML:

       

Code:

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

Vous pouvez lier la largeur à tout conteneur souhaité.

       ComboBoxItem1 ComboBoxItem2   

Pour obtenir exactement ce que vous essayez de faire avec le C # que vous avez écrit, je me pencherais sur l’imputation d’un IValueConverter ou d’un IMultiValueConverter.

Placez une zone de liste contenant le même contenu derrière la liste déroulante. Ensuite, appliquez une hauteur correcte avec une liaison comme celle-ci:

     

Dans mon cas, un moyen beaucoup plus simple semblait faire l’affaire, je viens d’utiliser un stackPanel supplémentaire pour envelopper la liste déroulante.

    

(a travaillé dans le studio visuel 2008)

Je cherchais moi-même la réponse lorsque je suis tombé sur la méthode UpdateLayout() que chaque UIElement possède.

C’est très simple maintenant, heureusement!

Il suffit d’appeler ComboBox1.Updatelayout(); après avoir défini ou modifié le ItemSource .

En ce qui me concerne, la solution pour étendre ComboBox.Width à toute la largeur de la colonne consistait à définir la largeur ColumnDefinition sur “*” au lieu de “Auto”:

         

L’approche d’Alun Harford, dans la pratique:

           foo bar fiuafiouhoiruhslkfhalsjfhalhflasdkf   

Il suffit d’append une largeur à la combo

  

Cela garde la largeur à l’élément le plus large mais seulement après avoir ouvert la boîte à options une fois.