मैं एक WPF कॉम्बो बॉक्स कैसे बना सकता हूं XAML में इसके सबसे व्यापक तत्व की चौड़ाई है?


103

मुझे पता है कि इसे कोड में कैसे करना है, लेकिन क्या यह एक्सएएमएल में किया जा सकता है?

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/... अपने प्रश्न चिह्नित करें "के रूप में दिए" अगर यह आपके सवाल का जवाब।
सुदीप

मैंने इस दृष्टिकोण को कोड में आज़माया है, लेकिन पाया है कि माप विस्टा और एक्सपी के बीच भिन्न हो सकता है। विस्टा पर, DesiredSize में आमतौर पर ड्रॉप डाउन एरो का आकार शामिल होता है लेकिन XP पर, अक्सर चौड़ाई में ड्रॉप-डाउन एरो शामिल नहीं होता है। अब, मेरे परिणाम हो सकते हैं क्योंकि मैं मूल विंडो दिखाई देने से पहले माप करने का प्रयास कर रहा हूं। अपडेट से पहले एक अपडेट लाइक () जोड़ना मदद कर सकता है लेकिन ऐप में अन्य दुष्प्रभाव पैदा कर सकता है। यदि आप साझा करने के लिए तैयार हैं, तो आप जिस समाधान के साथ आए हैं, उसे देखने में मेरी दिलचस्पी होगी।
jschroedl 17

आपने अपनी समस्या को कैसे हल किया?
एंड्रयू कलाश्निकोव

जवाबों:


31

यह बिना XAML में नहीं हो सकता है:

  • एक छिपे हुए नियंत्रण (एलन हनफोर्ड का जवाब) बनाना
  • ControlTemplate को काफी बदल रहा है। इस मामले में भी, एक आइटम संस्करण का एक छिपा हुआ संस्करण बनाने की आवश्यकता हो सकती है।

इसका कारण यह है कि डिफ़ॉल्ट कॉम्बोबॉक्स कंट्रोलटेम्पलेट्स जो मैंने पूरे (एयरो, लूना, आदि) में आए हैं, एक पॉपअप में सभी आइटमस्पेस को घोंसला बना लेते हैं। इसका मतलब है कि इन वस्तुओं के लेआउट को तब तक स्थगित कर दिया जाता है जब तक कि वे वास्तव में दिखाई नहीं देते।

इसका परीक्षण करने का एक आसान तरीका यह है कि डिफ़ॉल्ट ControlTemplate को संशोधित करने के लिए सबसे बाहरी कंटेनर (यह एयरो और लूना दोनों के लिए ग्रिड है) को PART_Popup की वास्तविक स्थिति में बांधें। जब आप ड्रॉप बटन पर क्लिक करते हैं तो आप कॉम्बो बॉक्स को स्वचालित रूप से इसकी चौड़ाई सिंक्रनाइज़ कर पाएंगे, लेकिन इससे पहले नहीं।

इसलिए जब तक आप लेआउट सिस्टम में एक माप संचालन को बाध्य नहीं कर सकते हैं (जो आप एक दूसरे नियंत्रण को जोड़कर कर सकते हैं), मुझे नहीं लगता कि यह किया जा सकता है।

हमेशा की तरह, मैं एक छोटे, सुरुचिपूर्ण समाधान के लिए खुला हूं - लेकिन इस मामले में एक कोड-पीछे या दोहरे नियंत्रण / ControlTemplate हैक एकमात्र समाधान हैं जो मैंने देखा है।


57

आप इसे सीधे Xaml में नहीं कर सकते, लेकिन आप इस संलग्न व्यवहार का उपयोग कर सकते हैं। (डिजाइनर में चौड़ाई दिखाई देगी)

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

यह क्या करता है कि यह ComWBox के लिए एक एक्सटेंशन विधि कहता है जिसे 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();
    }
}

यह विस्तार विधि कॉल करने की क्षमता भी प्रदान करती है

comboBox.SetWidthFromItems();

पीछे कोड में (जैसे ComboBox.Loaded घटना में)


+1, बढ़िया समाधान! मैं उसी तर्ज पर कुछ करने की कोशिश कर रहा था, लेकिन आखिरकार मैंने आपके कार्यान्वयन का इस्तेमाल किया (कुछ संशोधनों के साथ)
थॉमस लेवेस्क

1
अद्भुत धन्यवाद। इसे स्वीकृत उत्तर के रूप में चिह्नित किया जाना चाहिए। संलग्न गुणों की तरह लग रहा है हमेशा सब कुछ का रास्ता है :)
इग्नासियो सोलर गार्सिया

जहां तक ​​मेरा सवाल है सबसे अच्छा समाधान। मैंने इंटरनेट पर चारों ओर से कई तरकीबें आजमाईं, और आपका समाधान मुझे मिला सबसे अच्छा और आसान है। +1।
पियरसेबल

7
ध्यान दें कि यदि आपके पास एक ही विंडो में कई कॉम्बोबॉक्स हैं ( यह मेरे लिए ऐसा हुआ है जिससे कॉम्बोक्स बनाने वाली विंडो और कोड-पीछे के साथ उनकी सामग्री ), पॉपअप एक सेकंड के लिए दिखाई दे सकता है। मुझे लगता है कि ऐसा इसलिए है क्योंकि किसी भी "क्लोज पॉपअप" को कॉल करने से पहले कई "ओपन पॉपअप" संदेश पोस्ट किए जाते हैं। इसका समाधान यह है कि SetWidthFromItemsकिसी कार्रवाई / डेलीगेट का उपयोग करके पूरी विधि को अतुल्यकालिक बना दिया जाए और एक आईडल प्राथमिकता के साथ एक BeginInvoke (जैसा कि लोड की गई घटना में किया गया है)। इस तरह, कोई भी उपाय नहीं किया जाएगा, जबकि मेसज पंप खाली नहीं है, और इस प्रकार, कोई संदेश
इंटरलाकिंग

1
क्या मैजिक नंबर: double comboBoxWidth = 19;आपके कोड से संबंधित है SystemParameters.VerticalScrollBarWidth?
जेफ ब्यूलैक

10

हाँ, यह थोड़ा बुरा है।

मैंने जो कुछ अतीत में किया है, उसे एक ही समय में प्रत्येक आइटम को दिखाते हुए एक छिपी हुई सूची बॉक्स (एक ग्रिड पर उसके आइटम्सकंटेनरपेनल के साथ) को कंट्रोलटेम्पलेट में जोड़ना है, लेकिन उनकी दृश्यता के साथ छिपा हुआ है।

मैं किसी भी बेहतर विचार को सुनकर प्रसन्न हो जाऊंगा जो भयानक कोड-पीछे या आपके विचार पर भरोसा नहीं करता है कि यह समझने के लिए कि दृश्य (yuck!) को समर्थन देने के लिए चौड़ाई प्रदान करने के लिए एक अलग नियंत्रण का उपयोग करना होगा।


1
क्या यह दृष्टिकोण कॉम्बो को पर्याप्त आकार देगा ताकि चयनित आइटम होने पर सबसे व्यापक आइटम पूरी तरह से दिखाई दे? यह वह जगह है जहाँ मैंने समस्याएं देखी हैं।
jschroedl

8

उपरोक्त अन्य उत्तरों के आधार पर, यहाँ मेरा संस्करण है:

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

क्षैतिज क्षैतिजकरण = "वाम" युक्त नियंत्रण की पूरी चौड़ाई का उपयोग करके नियंत्रण को रोकता है। ऊंचाई = "0" आइटम नियंत्रण को छुपाता है।
मार्जिन = "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>

ध्यान दें कि इसके साथ आपको प्रत्येक कॉम्बो बॉक्स के लिए GrowConverter का एक अलग उदाहरण चाहिए, जब तक कि आप ग्रिड के SharedSizeScope सुविधा के समान उन्हें एक साथ आकार देना चाहते हैं।


1
अच्छा, लेकिन सबसे लंबी प्रविष्टि का चयन करने के बाद केवल "स्थिर"।
प्रिमैफैक्टर

1
सही बात। मैंने WinForms में इस बारे में कुछ किया था, जहां मैं कॉम्बो बॉक्स में सभी स्ट्रिंग्स को मापने के लिए टेक्स्ट एपीआई का उपयोग करूंगा, और उस के लिए खाते में न्यूनतम चौड़ाई सेट करूंगा। डब्ल्यूपीएफ में ऐसा करना काफी कठिन है, खासकर जब आपके आइटम तार नहीं हैं और / या एक बंधन से आ रहे हैं।
चीता

3

मैल्क के उत्तर का अनुसरण: मुझे वह कार्यान्वयन बहुत पसंद आया, मैंने इसके लिए एक वास्तविक व्यवहार लिखा। जाहिर है आपको Blend SDK की आवश्यकता होगी ताकि आप System.Windows.Interactivity को संदर्भित कर सकें।

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। जब कॉम्बोबॉक्स सक्षम नहीं होता है, तो माता-पिता के विकलांग होने के कारण, तब तक अस्थायी रूप से कॉम्बो बॉक्स को सक्षम करना भी संभव नहीं होता है जब तक कि माप समाप्त न हो जाए।
फ्लाइंग फॉक्स

1

ड्रॉपबॉक्स के पीछे समान सामग्री वाला एक लिस्टबॉक्स रखें। फिर इस तरह कुछ बंधन के साथ सही ऊंचाई लागू करें:

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

1

मेरे मामले में एक बहुत ही सरल तरीका चाल करने के लिए लग रहा था, मैंने बस कॉम्बोक्स को लपेटने के लिए एक अतिरिक्त स्टैकपैनेल का उपयोग किया।

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

(दृश्य स्टूडियो 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;
    }
}

विकलांगों पर भी काम करता है ComboBox


0

मैं अपने आप को जवाब की तलाश में था, जब मैं उस UpdateLayout()पद्धति के पार आया, जो प्रत्येक के UIElementपास है।

यह अब बहुत आसान है, शुक्र है!

ComboBox1.Updatelayout();आपके द्वारा सेट या संशोधित करने के बाद ही कॉल करें ItemSource


0

अभ्यास में एलुन हारफोर्ड का दृष्टिकोण:

<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

यह चौड़ाई को व्यापक तत्व तक रखता है लेकिन केवल एक बार कॉम्बो बॉक्स खोलने के बाद।

<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>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.