C# 如何使 WPF 组合框具有 XAML 中其最宽元素的宽度?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1034505/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I make a WPF combo box have the width of its widest element in XAML?
提问by Csupor Jen?
I know how to do it in code, but can this be done in XAML ?
我知道如何在代码中做到这一点,但这可以在 XAML 中完成吗?
Window1.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:
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;
}
}
}
采纳答案by micahtan
This can't be in XAML without either:
如果没有以下任何一项,就不能在 XAML 中:
- Creating a hidden control (Alan Hunford's answer)
- Changing the ControlTemplate drastically. Even in this case, a hidden version of an ItemsPresenter may need to be created.
- 创建隐藏控件(Alan Hunford 的回答)
- 彻底改变 ControlTemplate。即使在这种情况下,也可能需要创建 ItemsPresenter 的隐藏版本。
The reason for this is that the default ComboBox ControlTemplates that I've come across (Aero, Luna, etc.) all nest the ItemsPresenter in a Popup. This means that the layout of these items is deferred until they are actually made visible.
这样做的原因是我遇到的默认 ComboBox ControlTemplates(Aero、Luna 等)都将 ItemsPresenter 嵌套在 Popup 中。这意味着这些项目的布局被推迟,直到它们真正可见。
An easy way to test this is to modify the default ControlTemplate to bind the MinWidth of the outermost container (it's a Grid for both Aero and Luna) to the ActualWidth of PART_Popup. You'll be able to have the ComboBox automatically synchronize it's width when you click the drop button, but not before.
一个简单的测试方法是修改默认的 ControlTemplate 以将最外层容器(它是 Aero 和 Luna 的网格)的 MinWidth 绑定到 PART_Popup 的 ActualWidth。当您单击下拉按钮时,您将能够让 ComboBox 自动同步其宽度,但之前不会。
So unless you can force a Measure operation in the layout system (which you cando by adding a second control), I don't think it can be done.
因此,除非您可以在布局系统中强制执行 Measure 操作(您可以通过添加第二个控件来实现),否则我认为它无法完成。
As always, I'm open to an short, elegant solution -- but in this case a code-behind or dual-control/ControlTemplate hacks are the only solutions I have seen.
与往常一样,我对一个简短、优雅的解决方案持开放态度——但在这种情况下,代码隐藏或双控制/ControlTemplate hacks 是我见过的唯一解决方案。
回答by Alun Harford
Yeah, this one is a bit nasty.
是的,这个有点恶心。
What I've done in the past is to add into the ControlTemplate a hidden listbox (with its itemscontainerpanel set to a grid) showing every item at the same time but with their visibility set to hidden.
我过去所做的是在 ControlTemplate 中添加一个隐藏的列表框(其 itemscontainerpanel 设置为网格)同时显示每个项目,但它们的可见性设置为隐藏。
I'd be pleased to hear of any better ideas that don't rely on horrible code-behind or your view having to understand that it needs to use a different control to provide the width to support the visuals (yuck!).
我很高兴听到任何更好的想法,这些想法不依赖于可怕的代码隐藏,或者您的视图必须了解它需要使用不同的控件来提供宽度来支持视觉效果(糟糕!)。
回答by Matze
Put an listbox containing the same content behind the dropbox. Then enforce correct height with some binding like this:
在 Dropbox 后面放置一个包含相同内容的列表框。然后用一些这样的绑定来强制执行正确的高度:
<Grid>
<ListBox x:Name="listBox" Height="{Binding ElementName=dropBox, Path=DesiredSize.Height}" />
<ComboBox x:Name="dropBox" />
</Grid>
回答by Cheetah
I ended up with a "good enough" solution to this problem being to make the combo box never shrink below the largest size it held, similar to the old WinForms AutoSizeMode=GrowOnly.
我最终找到了一个“足够好”的解决方案来解决这个问题,即让组合框永远不会缩小到它所容纳的最大尺寸以下,类似于旧的 WinForms AutoSizeMode=GrowOnly。
The way I did this was with a custom value converter:
我这样做的方法是使用自定义值转换器:
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();
}
}
Then I configure the combo box in XAML like so:
然后我在 XAML 中配置组合框,如下所示:
<Whatever>
<Whatever.Resources>
<my:GrowConverter x:Key="grow" />
</Whatever.Resources>
...
<ComboBox MinWidth="{Binding ActualWidth,RelativeSource={RelativeSource Self},Converter={StaticResource grow}}" />
</Whatever>
Note that with this you need a separate instance of the GrowConverter for each combo box, unless of course you want a set of them to size together, similar to the Grid's SharedSizeScope feature.
请注意,对于每个组合框,您需要一个单独的 GrowConverter 实例,当然,除非您希望一组它们一起调整大小,类似于 Grid 的 SharedSizeScope 功能。
回答by Fredrik Hedblad
You can't do it directly in Xaml but you can use this Attached Behavior. (The Width will be visible in the Designer)
您不能直接在 Xaml 中执行此操作,但可以使用此附加行为。(宽度将在设计器中可见)
<ComboBox behaviors:ComboBoxWidthFromItemsBehavior.ComboBoxWidthFromItems="True">
<ComboBoxItem Content="Short"/>
<ComboBoxItem Content="Medium Long"/>
<ComboBoxItem Content="Min"/>
</ComboBox>
The Attached Behavior ComboBoxWidthFromItemsProperty
附加行为 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);
}
}
What it does is that it calls an extension method for ComboBox called SetWidthFromItems which (invisibly) expands and collapses itself and then calculates the Width based on the generated ComboBoxItems. (IExpandCollapseProvider requires a reference to UIAutomationProvider.dll)
它的作用是调用一个名为 SetWidthFromItems 的 ComboBox 扩展方法,该方法(不可见地)展开和折叠自身,然后根据生成的 ComboBoxItems 计算宽度。(IExpandCollapseProvider 需要引用 UIAutomationProvider.dll)
Then extension method SetWidthFromItems
然后扩展方法 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();
}
}
This extension method also provides to ability to call
这个扩展方法还提供了调用的能力
comboBox.SetWidthFromItems();
in code behind (e.g in the ComboBox.Loaded event)
在后面的代码中(例如在 ComboBox.Loaded 事件中)
回答by Nikos Tsokos
In my case a much simpler way seemed to do the trick, I just used an extra stackPanel to wrap the combobox.
在我的情况下,似乎有一种更简单的方法可以解决问题,我只是使用了一个额外的 stackPanel 来包装组合框。
<StackPanel Grid.Row="1" Orientation="Horizontal">
<ComboBox ItemsSource="{Binding ExecutionTimesModeList}" Width="Auto"
SelectedValuePath="Item" DisplayMemberPath="FriendlyName"
SelectedValue="{Binding Model.SelectedExecutionTimesMode}" />
</StackPanel>
(worked in visual studio 2008)
(曾在visual studio 2008工作)
回答by Gaspode
Based on the other answers above, here's my version:
根据上面的其他答案,这是我的版本:
<Grid HorizontalAlignment="Left">
<ItemsControl ItemsSource="{Binding EnumValues}" Height="0" Margin="15,0"/>
<ComboBox ItemsSource="{Binding EnumValues}" />
</Grid>
HorizontalAlignment="Left" stops the controls using the full width of the containing control.
Height="0" hides the items control.
Margin="15,0" allows for additional chrome around combo-box items (not chrome agnostic I'm afraid).
HorizontalAlignment="Left" 使用包含控件的整个宽度停止控件。Height="0" 隐藏项目控件。
Margin="15,0" 允许在组合框项目周围添加额外的 chrome(恐怕不是 chrome 不可知论者)。
回答by Mike Post
A follow up to Maleak's answer: I liked that implementation so much, I wrote an actual Behavior for it. Obviously you'll need the Blend SDK so you can reference System.Windows.Interactivity.
对 Maleak 回答的跟进:我非常喜欢这个实现,我为它写了一个实际的行为。显然,您将需要 Blend SDK,以便您可以引用 System.Windows.Interactivity。
XAML:
XAML:
<ComboBox ItemsSource="{Binding ListOfStuff}">
<i:Interaction.Behaviors>
<local:ComboBoxWidthBehavior />
</i:Interaction.Behaviors>
</ComboBox>
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<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();
}
}
}
回答by Sinker
I was looking for the answer myself, when I came across the UpdateLayout()
method that every UIElement
has.
当我遇到UpdateLayout()
每个人UIElement
都有的方法时,我自己正在寻找答案。
It's very simple now, thankfully!
现在非常简单,谢天谢地!
Just call ComboBox1.Updatelayout();
after you set or modify the ItemSource
.
ComboBox1.Updatelayout();
设置或修改后调用即可ItemSource
。
回答by Jan Van Overbeke
Alun Harford's approach, in practice :
阿伦哈福德的做法,在实践中:
<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>fiuafiouhtheitroaduhslkfhalsjfhalhflasdkf</ComboBoxItem>
</ComboBox>
</Grid>