【问题标题】:How to make a "SelectButton" with Button, Extender and ListBox to have width it needs?如何使用 Button、Extender 和 ListBox 制作“SelectButton”以获得所需的宽度?
【发布时间】:2016-06-29 20:11:17
【问题描述】:

我正在尝试使用 SelectButton (https://gist.github.com/loraderon/580405),但我需要为其指定 MinWidth。否则它的宽度就是扩展器的宽度。删除 ColumnSpan 或设置第一列 Auto 并没有成功。我真的希望它在列表 + 扩展符号中始终具有最宽元素的宽度。

<UserControl x:Class="loraderon.Controls.SelectButton"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:my="clr-namespace:loraderon.Controls"
         mc:Ignorable="d" 
         SizeChanged="UserControl_SizeChanged"
         d:DesignHeight="30" d:DesignWidth="100">
<Grid
    x:Name="SplitGrid"
    >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="23" />
    </Grid.ColumnDefinitions>
    <Button
        x:Name="Button"
        Click="Button_Click"
        Grid.ColumnSpan="2"
        Padding="0"
        HorizontalContentAlignment="Left"
        >
        <ContentControl
            x:Name="ButtonContent"
            HorizontalContentAlignment="Center"
            ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}"
            />
    </Button>
    <Expander
        x:Name="Expander"
        Expanded="Expander_Expanded"
        Collapsed="Expander_Collapsed"
        Grid.Column="1"
        VerticalAlignment="Center"
        IsExpanded="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}"
        />  
    <Popup
        IsOpen="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}"
        PlacementTarget="{Binding ElementName=Button}"
        PopupAnimation="Fade"
        StaysOpen="False"
        >
        <ListBox
            x:Name="ListBox"
            SelectionMode="Single"
            SelectionChanged="ListBox_SelectionChanged"
            SelectedIndex="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=SelectedIndex, Mode=TwoWay}"
            ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}"
            ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemsSource}"
            />
    </Popup>
</Grid>
</UserControl

编辑:我放置控件的窗口有:

SizeToContent="WidthAndHeight"

这导致下面的两个答案都不起作用。将按钮放置在各种控件/容器中时,是否有更强大的解决方案?似乎控件的构建方式不是很健壮。弹出窗口不是可视树的一部分使其成为一个糟糕的选择。

【问题讨论】:

  • 使用的时候不能直接设置Width吗?
  • 感谢您的回答,但当然不是。我不知道这些项目在不同语言中的使用时间有多长。如果未指定大小,则 plus 控件应正确调整其内容的大小。
  • 也许你可以在用户控件和网格中偷偷添加一些水平拉伸属性。
  • 我所做的是 但这发生得太晚了。延长一次就可以了,但一开始就不行了
  • 你不能将 MinWidth 设置为 ListBox 宽度,如 MinWidth="{Binding Width, ElementName=ListBox}" 并在必要时使用可视状态管理器进行更改,否则我遗漏了什么?

标签: wpf xaml


【解决方案1】:

简单的部分是绑定到 ListBox 的 ActualWidth

   <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding ElementName=ListBox, Path=ActualWidth}"/>
        <ColumnDefinition Width="23" />
    </Grid.ColumnDefinitions>

棘手的部分是,由于 ListBox 位于 Popup 中,具有自己的可视化树 (Remarks), 仅当 IsOpen 设置为 true 时才会呈现。

解决方法是在加载控件时快速打开/关闭

public SelectButton()
{
    InitializeComponent();
    Loaded += (o, e) => Initialize();
}

void Initialize()
{
    IsExpanded = true;
    IsExpanded = false;
}

以及更新的 Expander_Expanded 方法

private DateTime startUpTime = DateTime.Now;
private DateTime collapsedAt = DateTime.MinValue;

private void Expander_Expanded(object sender, RoutedEventArgs e)
{
    if (DateTime.Now - startUpTime <= TimeSpan.FromMilliseconds(200))
    {
        IsExpanded = true;
        return;
    }
    if (DateTime.Now - collapsedAt <= TimeSpan.FromMilliseconds(200))
    {
        Expander.IsExpanded = false;
        IsExpanded = false;
        return;
    }
    IsExpanded = true;
}

编辑

事实证明 200 毫秒的时间跨度可能太小,具体取决于所使用的系统,添加了一个更强大的解决方案

private bool startUp = true;
private DateTime collapsedAt = DateTime.MinValue;

private void Expander_Expanded(object sender, RoutedEventArgs e)
{
    if (startUp)
    {
        IsExpanded = true;
        startUp = false;
        return;
    }
    if (DateTime.Now - collapsedAt <= TimeSpan.FromMilliseconds(200))
    {
        Expander.IsExpanded = false;
        IsExpanded = false;
        return;
    }
    IsExpanded = true;
}

【讨论】:

  • 谢谢!我会在几个小时内调查这个。打开是否涉及 GUI 中的可见闪烁?
  • @matti 通常不会。但是既然你问它确实发生过一次,当我的计算机内存不足(变得缓慢)时,在重新编译后我可以看到它发生了。
  • 不起作用。 Expander_Expanded 仅在手动展开时调用。
  • @matti 在 SelectButton.xaml.cs 中将 IsExpanded 设置为 true 后(加载 SelectButton 时),从 SelectButton.xaml(在 Expander 中)调用 Expander_Expanded。要复制我的工作代码,您可以启动一个名为 WpfSelectButton 的新 WPF 应用程序并实现我在 Github 上发布的 4 个文件。
  • 谢谢!我目前正在研究从窗口中删除 SizeToContent="WidthAndHeight" 时起作用的其他解决方案。这可能是为什么没有调用 Expander_Expanded 的原因吗?我稍后会尝试您编辑的解决方案。
【解决方案2】:

这不漂亮,但工作。由于您已经进行了 Code-Behind,这可能符合您的需求:

首先,ItemsSourceProperty。将其更改为:

 public static readonly DependencyProperty ItemsSourceProperty =
  DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(SelectButton), new PropertyMetadata(ItemsSourceChanged ));

二、准备构造函数:

public SelectButton() {
      InitializeComponent();
      this.ListBox.Loaded += this.ListBoxOnLoaded;
    }

三、实现ItemnsSourceChanged-方法:

private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
      var self = d as SelectButton;
      self.ListBoxOnLoaded(self.ListBox, new RoutedEventArgs());
    }

第四,施展魔法:

private void ListBoxOnLoaded(object sender, RoutedEventArgs routedEventArgs) {
      var lb = sender as ListBox;
      lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
      this.col1.MinWidth = lb.DesiredSize.Width;

    }

最后但同样重要的是,编辑 XAML:

<Grid x:Name="SplitGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" Name="col1"  />
            <ColumnDefinition Width="23" />
        </Grid.ColumnDefinitions>

加载列表框后,我们只需自己进行测量并将所需的大小应用于第一列。

希望对你有帮助:)

【讨论】:

  • 不起作用。 ListBoxOnLoaded 结果 MinWidth 为 4.0
  • 我知道。这发生在第一次运行时,在 ItemsSource 绑定之前。实现并调用ItemsSourceChanged-方法后,测量将起作用;)
  • 两次相同。我确实很喜欢你的回答。第二次从 ItemsSourceChanged 调用它。
  • 您确定,您也修改了 xaml 吗?您能否确认,您的 ItemsSource 包含项目?在大约一个小时内上传一个工作 .sln
  • 是的。我只是要写信给你,我知道为什么它不起作用。列表框中有 0 项。 2 在项目源中。我尝试检查原因。
【解决方案3】:

这是一个可怕的答案,但可能会给某人一个想法。我在按钮内容所在的同一位置创建了一个不可见的列表框,并将 Grid.Column="0" MinWidth 绑定到它的 ActualWidth。

不知何故,这有点太宽了。 ListBox 的宽度太宽,无法分配给 Grid.Column="0"。弹出列表框中的项目要窄得多。其中的最大值应该是分配给 Grid.Column="0" 的宽度。

我还尝试在那里设置一个按钮并为其内容创建额外的依赖属性。那是最好看的(尺寸很完美),但是你必须最好知道所有物品及其不同语言的尺寸或至少一件物品。这当然是很大的劣势。

编辑:如果可以通过 ContentControl/ContentPresenter 以某种方式避免 2 ListBox 来实现同样的效果,那就更好了。

EDIT2:这不起作用。 Width 是第一个元素的宽度,因此 order 或 ItemsSource 是相关的。

这里是 xaml:

<UserControl x:Class="loraderon.Controls.SelectButton"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:my="clr-namespace:loraderon.Controls"
         mc:Ignorable="d"
         SizeChanged="UserControl_SizeChanged"
         d:DesignHeight="30" d:DesignWidth="100">
  <Grid
      x:Name="SplitGrid"
    >
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" MinWidth="{Binding ActualWidth, ElementName=ContentListBox}"/>
      <ColumnDefinition Width="23" />
    </Grid.ColumnDefinitions>
    <Button
        x:Name="Button"
        Click="Button_Click"
        Grid.ColumnSpan="2"
        Padding="0"
        HorizontalContentAlignment="Left"
        >
      <ContentControl
          x:Name="ButtonContent"
          HorizontalContentAlignment="Center"
          ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}"
            />
    </Button>
    <ListBox
        Grid.Column="0"
            x:Name="ContentListBox"
            Visibility="Hidden"
            MaxHeight="{Binding ActualHeight, ElementName=Button}"
            HorizontalAlignment="Stretch"
            ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}"
            ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemsSource}"/>
    <Expander
        x:Name="Expander"
        Expanded="Expander_Expanded"
        Collapsed="Expander_Collapsed"
        Grid.Column="1"
        VerticalAlignment="Center"
        IsExpanded="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}"
        />
    <Popup
        IsOpen="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}"
        PlacementTarget="{Binding ElementName=Button}"
        PopupAnimation="Fade"
        StaysOpen="False"
        >
      <ListBox
          x:Name="ListBox"
          SelectionMode="Single"
          SelectionChanged="ListBox_SelectionChanged"
          SelectedIndex="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=SelectedIndex, Mode=TwoWay}"
          ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}"
          ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemsSource}"
            />
    </Popup>
  </Grid>
</UserControl

【讨论】:

  • 如果 ListBox 只能定义一次,那就太好了。这可以作为一种资源来完成吗?我尝试过,但无法实现。任何使该解决方案更好的答案(例如避免 2 个 ListBox:es)都将被接受。
【解决方案4】:

您可以创建一个临时的ListBox 并对其进行测量以找到所需的元素大小。

最适合计算大小的地方是ItemsSource 属性更改时。您可以通过修改依赖属性来实现这一点:

public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(SelectButton), new PropertyMetadata(ItemSourceChanged));

ItemSourceChanged 方法中,你可以创建一个临时的ListBox,让它拥有你的物品,然后测量它:

private static void ItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ContentControl control = new ContentControl();
    ListBox listBox = new ListBox();
    control.Content = listBox;

    IEnumerable enumerable = e.NewValue as IEnumerable;
    SelectButton selectButton = d as SelectButton;

    foreach (var item in enumerable)
    {
        listBox.Items.Add(item);
    }

    listBox.Measure(new Size(Double.MaxValue, Double.MaxValue));
    selectButton.Button.Width = listBox.DesiredSize.Width;
}

这里control.Content = listBox; 是必要的。如果ListBox 未包含在控件中,则所需大小始终返回 0。

【讨论】:

  • 不起作用。如果第二个项目像“项目非常非常长”,则控件不够长。如果 ButtonContent 而不是 Button 并删除 UserControl_SizeChanged,我也尝试设置宽度
  • 你确定吗?我可以成功创建一个具有最长项目长度的按钮。我可以向您发送一个工作示例。您如何使用窗口中的控件?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-27
相关资源
最近更新 更多