【问题标题】:Sliding text in combobox在组合框中滑动文本
【发布时间】:2017-08-03 02:09:25
【问题描述】:

如果它的长度大于组合框的宽度,我想在组合框中滑动所选项目的文本(选框文本)。它可以是自动的,也可以是用户将鼠标放在组合框上时。问题是我完全不知道该怎么做。也许可以通过渲染转换(之前定义的文本块)来做到这一点?还是有故事板?

这是我需要修改的 xaml

<DataGrid.ColumnHeaderStyle>
     <Style TargetType="{x:Type DataGridColumnHeader}">
          <Setter Property="ContentTemplate" >
              <Setter.Value>
                  <DataTemplate DataType="DataGridColumnHeader"  >
                      <ComboBox ItemContainerStyle="{StaticResource SingleSelectionComboBoxItem}" DisplayMemberPath="Oggetto" Width="100" Height="20" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.Selezione, UpdateSourceTrigger=LostFocus}"  SelectionChanged="SingleSelectionComboBox_SelectionChanged"/>
                  </DataTemplate>
              </Setter.Value>
          </Setter>
      </Style>
</DataGrid.ColumnHeaderStyle>

编辑:问题是我不知道我应该在情节提要中定位哪些属性

EDIT2:我采用了组合框的模板并像这样修改了文本框部分:

<Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <EventTrigger RoutedEvent="MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation From="0" To="100" Duration="00:00:10" Storyboard.TargetProperty="X" Storyboard.TargetName="transferCurreny" RepeatBehavior="Forever"  />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Style.Triggers>
            <Setter Property="RenderTransform">
                <Setter.Value>
                    <TranslateTransform x:Name="transferCurreny" X="0"/>
                </Setter.Value>
            </Setter>

问题是这似乎没有效果

编辑 3:我意识到我必须使用使用我上面提到的样式的模板

            <ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}">
            <Grid x:Name="Placement" SnapsToDevicePixels="true">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2"
               IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
               PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"
               Placement="Bottom">
                    <Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent"
                                           MaxHeight="{TemplateBinding MaxDropDownHeight}"
                                           MinWidth="{Binding ActualWidth, ElementName=Placement}">
                        <Border x:Name="DropDownBorder"
                        BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
                        BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
                            <ScrollViewer x:Name="DropDownScrollViewer">
                                <Grid RenderOptions.ClearTypeHint="Enabled">
                                    <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                        <Rectangle x:Name="OpaqueRect"
                                           Fill="{Binding Background, ElementName=DropDownBorder}"
                                           Height="{Binding ActualHeight, ElementName=DropDownBorder}"
                                           Width="{Binding ActualWidth, ElementName=DropDownBorder}" />
                                    </Canvas>
                                    <ItemsPresenter x:Name="ItemsPresenter"
                                            KeyboardNavigation.DirectionalNavigation="Contained"
                                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                </Grid>
                            </ScrollViewer>
                        </Border>
                    </Themes:SystemDropShadowChrome>
                </Popup>
                <Themes:ListBoxChrome x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="{TemplateBinding BorderThickness}"
                              Background="{TemplateBinding Background}" Grid.ColumnSpan="2"
                              RenderMouseOver="{TemplateBinding IsMouseOver}"
                              RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" />
            <TextBox x:Name="PART_EditableTextBox"
                 HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                 IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"
                 Margin="{TemplateBinding Padding}" Style="{StaticResource ComboBoxEditableTextBox}"
                 VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" >
            </TextBox>
                <ToggleButton Grid.Column="1"
                      IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                      Style="{StaticResource ComboBoxToggleButton}" />
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsKeyboardFocusWithin" Value="true">
                    <Setter Property="Foreground" Value="Black" />
                </Trigger>
                <Trigger Property="IsDropDownOpen" Value="true">
                    <Setter Property="RenderFocused" TargetName="Border" Value="true" />
                </Trigger>
                <Trigger Property="HasItems" Value="false">
                    <Setter Property="Height" TargetName="DropDownBorder" Value="95" />
                </Trigger>
                <Trigger Property="IsEnabled" Value="false">
                    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                    <Setter Property="Background" Value="#FFF4F4F4" />
                </Trigger>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsGrouping" Value="true" />
                        <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
                    </MultiTrigger.Conditions>
                    <Setter Property="ScrollViewer.CanContentScroll" Value="false" />
                </MultiTrigger>
                <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
                    <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5" />
                    <Setter Property="Color" TargetName="Shdw" Value="#71000000" />
                </Trigger>
                <Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
                    <Setter Property="Canvas.Top" TargetName="OpaqueRect"
                    Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}" />
                    <Setter Property="Canvas.Left" TargetName="OpaqueRect"
                    Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

文本框部分是使用该样式的部分。但是,当我在组合框中执行Template="{StaticResource ComboBoxEditableTemplate}" 时,我再也看不到所选项目,即使它不是空的

【问题讨论】:

  • 故事板绝对是要走的路。您是否尝试过使用 Storyboard 进行任何操作?
  • 不是真的,因为我从未使用过它们。在尝试任何事情之前,我想知道哪个控件适合解决我的问题
  • 您会考虑使用弹出窗口吗?例如,当项目的长度大于 30 时(例如)显示 ToolTip 并包含整个文本。
  • 工具提示是我的 B 计划。我只是想做一些不同的事情。我目前正在研究如何修改组合框的“PART_EditableTextBox”
  • 我明白了,你正在寻找正确的部分。我认为ComboBox 正在使用相同的模板来显示所选项目和下拉列表中的项目。所以这可能很棘手。您可以随时订阅选择更改事件并尝试使用后面的代码滚动 TextBox?中的文本?

标签: c# wpf combobox


【解决方案1】:

2002 年 8 月更新

基本上,为了避免您面临的动画问题 - 您需要将 ScrollViewer 作为包装器添加到内容站点或选择框 TextBlock。您可以通过为您的ComboBox 使用自定义模板选择器来做到这一点,如下定义,或者提供带有ScollViewer 的自定义项目模板。

其次,您可以在 C# 中创建自定义 ComboBox 控件来绑定上述模板选择器/项目模板并在 MouseEnterEvent 上应用幻灯片动画;或在 XAML 本身中实现相同的功能。

对于这个例子 - 已经在 C# 中实现了控件,并提供了 XAML 中的示例用法。

自定义控件代码和模板选择器

public class SlidingComboBox : ComboBox
{
    public static readonly DependencyProperty SlideForeverProperty = DependencyProperty.Register("SlideForever", typeof(bool), typeof(SlidingComboBox), new FrameworkPropertyMetadata(false));
    public bool SlideForever
    {
        get { return (bool)GetValue(SlideForeverProperty); }
        set { SetValue(SlideForeverProperty, value); }
    }

    protected ContentPresenter _parent;
    protected DoubleAnimation _animation;
    protected TranslateTransform _translate;
    protected Storyboard _storyBoard;

    public SlidingComboBox()
    {
        Loaded += ExComboBox_Loaded;
        ClipToBounds = true;

        //assign template selector - just to re-template ContentSite / selection box 
        //uncomment this code - if you want to default-template for popup-items
        //ItemTemplateSelector = new SlidingComboBoxItemTemplateSelector();        
    }

    private void ExComboBox_Loaded(object sender, RoutedEventArgs e)
    {
        Loaded -= ExComboBox_Loaded;

        //get content-site holder/parent
        _parent = this.GetChildOfType<ContentPresenter>();

        //setup slide animation
        _animation = new DoubleAnimation()
        {
            From = 0,
            RepeatBehavior = SlideForever ? RepeatBehavior.Forever : new RepeatBehavior(1), //repeat only if slide-forever is true
            AutoReverse = SlideForever
        };

        //create storyboard
        _storyBoard = new Storyboard();
        _storyBoard.Children.Add(_animation);
        Storyboard.SetTargetProperty(_animation, new PropertyPath("RenderTransform.(TranslateTransform.X)"));
    }

    protected override void OnMouseEnter(MouseEventArgs e)
    {
        //get actual textblock that renders the selected value
        var textBlock = _parent.GetChildOfType<TextBlock>();
        //and translate-transform for animation
        textBlock.RenderTransform = _translate = new TranslateTransform();

        //start animation only if text-block width is greater than parent
        if (_parent.ActualWidth < textBlock.ActualWidth)
        {
            _animation.Duration = TimeSpan.FromMilliseconds(((int)textBlock.Text?.Length * 100));
            _animation.To = _parent.ActualWidth - textBlock.ActualWidth;
            _storyBoard.Begin(textBlock);
        }

        base.OnMouseEnter(e);
    }

    protected override void OnMouseLeave(MouseEventArgs e)
    {
        //stop animation once mouse pointer is off the control
        _storyBoard.Stop();

        //reset render state
        var textBlock = _parent.GetChildOfType<TextBlock>();
        textBlock.RenderTransform = _translate = new TranslateTransform();

        base.OnMouseLeave(e);
    }

}

public class SlidingComboBoxItemTemplateSelector : DataTemplateSelector
{
    DataTemplate _selectedItemTemplate;

    public SlidingComboBoxItemTemplateSelector()
    {
        //create datatemplate with ScrollViewer and TextBlock as child
        var textBlock = new FrameworkElementFactory(typeof(TextBlock));
        textBlock.SetValue(TextBlock.TextWrappingProperty, TextWrapping.NoWrap);
        textBlock.SetBinding(TextBlock.TextProperty, new Binding("SelectedValue")
        {
            RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ComboBox) }
        });
        var scrollViewer = new FrameworkElementFactory(typeof(ScrollViewer));
        scrollViewer.SetValue(ScrollViewer.CanContentScrollProperty, true);
        scrollViewer.SetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Hidden);
        scrollViewer.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled);
        scrollViewer.AppendChild(textBlock);

        _selectedItemTemplate = new DataTemplate
        {
            VisualTree = scrollViewer
        };
    }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        ComboBoxItem comboBoxItem = container.GetVisualParent<ComboBoxItem>();
        if (comboBoxItem == null)
        {
            //send back only if template requested for ContentSite, and not for combo-box item(s)
            return _selectedItemTemplate;
        }
        return null;
    }
}

/// <summary>
/// VisualTree helper
/// </summary>
public static class HelperExtensions
{
    public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
    {
        if (depObj == null) return null;

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            var child = VisualTreeHelper.GetChild(depObj, i);

            var result = (child as T) ?? GetChildOfType<T>(child);
            if (result != null) return result;
        }
        return null;
    }

    public static T GetVisualParent<T>(this DependencyObject child) where T : Visual
    {
        while ((child != null) && !(child is T))
        {
            child = VisualTreeHelper.GetParent(child);
        }
        return child as T;
    }
}

示例用法:

<Window x:Class="MarqueeSample.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:MarqueeSample"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="400">
    <Grid Margin="20">
        <DataGrid AutoGenerateColumns="False" IsReadOnly="True" Width="225" VerticalAlignment="Center">
            <DataGrid.ItemsSource>
                <col:Hashtable>
                    <col:ArrayList x:Key="TestData1">
                        <sys:String>Tiny</sys:String>
                        <sys:String>Keep calm and love programming</sys:String>
                    </col:ArrayList>
                    <col:ArrayList x:Key="TestData2">
                        <sys:String>Sample string</sys:String>
                        <sys:String>Another string to test</sys:String>
                    </col:ArrayList>
                </col:Hashtable>
            </DataGrid.ItemsSource>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Width="100" Binding="{Binding Key}" />
                <DataGridTemplateColumn Header="Value" Width="100">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <local:SlidingComboBox ItemsSource="{Binding Value}">
                                <local:SlidingComboBox.ItemTemplate>
                                    <DataTemplate>
                                        <ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled">
                                            <TextBlock Text="{Binding}" />
                                        </ScrollViewer>
                                    </DataTemplate>
                                </local:SlidingComboBox.ItemTemplate>
                            </local:SlidingComboBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

您还可以使用 SlideForever 属性来操作 RepeatBehaviour。

<local:SlidingComboBox ItemsSource="{Binding Value}" SlideForever="True" />

【讨论】:

  • 我给了你赏金,因为它的工作效率为 99%。唯一的问题是我有这个例外:不能同时设置 ItemTemplateSelector 和 Display member path。当一个为非空时,必须先将其清除,然后才能设置另一个
  • 据我了解,基本上是因为 DisplayMemberPath 是一个 ItemTemplate。所以我想我应该在模板选择器中添加一个绑定?
  • 我修复了在 xaml 中覆盖项目模板的问题,同时保留了您的自定义事件。再次感谢
  • @DanieleSartori 你是对的——我们可以使用带有组合框的自定义 ItemTemplate 而不是模板选择器——但是你需要确保你的自定义模板有一个 ScrollViewer 才能使动画工作;我已经相应地更新了答案。
【解决方案2】:

我一直在研究这个问题,我想我有你的解决方案。您应该在 ComboBox ContentPresenter 上结合 RenderTransformStoryboard(这是显示当前选定项目的内容)

<ComboBox Grid.Row="1" Name="MyComboBox" Width="200">
        <ComboBox.Resources>
            <Style TargetType="ContentPresenter">
                <Setter Property="RenderTransform">
                    <Setter.Value>
                        <TranslateTransform X="0" Y="0" />
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <EventTrigger RoutedEvent="Window.Loaded">
                        <EventTrigger.Actions>
                            <BeginStoryboard x:Name="ScrollItem">
                                <Storyboard RepeatBehavior="Forever">
                                    <DoubleAnimation Duration="00:00:5" From="0" To="200" Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" />
                                    <DoubleAnimation Duration="00:00:5" BeginTime="00:00:5" From="-200" To="0" Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </ComboBox.Resources>
        <ComboBoxItem>
            I am combobox value 1
        </ComboBoxItem>
        <ComboBoxItem>
            I am combobox value 2, Hello!
        </ComboBoxItem>
    </ComboBox>

拥有大小为 200 的 ComboBox,从 0 滚动到 200,然后从 -200 滚动到 0,将文本从控件的右侧滚动到左侧。 (如果您愿意,可以删除第二个 DoubleAnimation 并将 AutoReverse 设置为 True 以使文本反弹回来,如果您愿意的话。这不会围绕对控件来说太大的项目进行编码,您将需要为 ComboBox 编写一些代码,以便它决定当前选择的 Item 是否太大,并从后面的代码(或者可能是自定义 ComboBox 类)动态打开/关闭情节提要。

【讨论】:

  • 这几乎可以工作了。但是我不明白为什么即使在滚动时文本仍然被剪切。
  • 我认为即使文本滚动,由于我的组合框是数据网格标题的一部分,会发生一些奇怪的效果:1-文本滚动但保持剪切(就像它显示在框中时一样) 2-如果您选择多于一项,它们都会滚动,但在整行中,它们不限于它们所属的单个组合框(请参见链接中的数字)imgur.com/mfNoM1d
  • 我已将事件设置为在窗口加载上,因此它们只会永远滚动。 RenderTransform 实际上纯粹操纵渲染的输出,问题是它在应用 RenderTransform 之前做出的所有基于布局的决策(包括应该剪切多少文本)。这很棘手......我觉得自定义组合框类自己决定要编写的文本将是最好的前进道路。
【解决方案3】:

首先遍历组合框的所有项目,通过将文本分配给标签来检查每个项目的宽度。然后,每次检查宽度,如果当前项的宽度大于以前的项,则更改最大宽度。

   int DropDownWidth(ComboBox myCombo)
   {
   int maxWidth = 0;
   int temp = 0;
   Label label1 = new Label();

   foreach (var obj in myCombo.Items)
   {
       label1.Text = obj.ToString();
       temp = label1.PreferredWidth;
       if (temp > maxWidth)
       {
           maxWidth = temp;
       }
   }
   label1.Dispose();
   return maxWidth;           
}

private void window_loaded(object sender, EventArgs e)
{
    comboBox1.DropDownWidth = DropDownWidth(comboBox1);
}

int DropDownWidth(ComboBox myCombo)
{
     int maxWidth = 0, temp = 0;
     foreach (var obj in myCombo.Items)
     {
         temp = TextRenderer.MeasureText(obj.ToString(), myCombo.Font).Width;
         if (temp > maxWidth)
         {
             maxWidth = temp;
         }
     }
     return maxWidth;
 }

感谢那些从这里得到它的人 --> Auto-width of ComboBox's content

【讨论】:

  • 如果我理解正确,您建议增加组合框本身的宽度。然而,这不是我需要的。我需要组合框保持原样,只有组合框内的文本(不在下拉列表内),即所选项目,应该滑动(因为如果它的长度大于组合框的宽度,那么文本将被剪切)
  • 您的示例适用于 WinForms 而不是 WPF
  • 我知道这一点,但几乎没有区别,所以我认为这还不错
  • 也许您可以根据问题的要求重新编写在 WPF 中有效的答案
猜你喜欢
  • 2011-09-27
  • 1970-01-01
  • 2012-08-02
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多