Sliding text in combobox

喜夏-厌秋 提交于 2020-01-01 06:49:11

问题


I would like to slide the text (marquee text) of the selected item in a combobox, if it's lenght is bigger than the width of the combobox. It can be either automatical or when the user put the mouse over the combobox. The problem is that i have absolutely no idea on how to do that. It's maybe possible to do that with a render transform (previous definition of a textblock inside it)? or with a storyboard?

Here is the xaml that i need to modify

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

EDIT: the problem is that i don't know which properties should i target in the storyboard

EDIT2: i took the template of the combobox and modified the text box section like this :

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

The problem is that this seems to have no effect

EDIT 3: i realized that i had to use the template that use the style i mentioned above

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

the textbox section is the one that use the style. However when i do Template="{StaticResource ComboBoxEditableTemplate}" in the combobox, i cannot see the selected item anymore, even if it's not null


回答1:


Updated 08/02

Basically in order to avoid the animation issues you are facing - you will need to add a ScrollViewer as a wrapper to the content-site or selection-box TextBlock. You can either do that by using a custom template selector for your ComboBox so as defined below, or provide a custom item-template with ScollViewer.

Secondly, you can either create a custom ComboBox control in C# to bind the above template-selector/item-template and apply slide-animation on MouseEnterEvent; or implement the same in XAML itself.

For this example - have implemented the control in C#, and provided a sample usage in XAML.

Code for custom control, and template selector

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

Sample usage:

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

You can also use SlideForever property to manipulate RepeatBehaviour.

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



回答2:


I've been looking into this and I think I have your solution. You should combine both a RenderTransform and a Storyboard on the ComboBox ContentPresenter (this is what displays the currently selected item)

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

Having the ComboBox of size 200, scrolling from 0 to 200, then -200 to 0, scrolls the text off the right hand side of the control, and in the left hand side. (You can drop the 2nd DoubleAnimation if you like and set AutoReverse to True to cause the text to bounce back in if you'd rather that. This does not code you around items that are too big for the control, you will need to write some code for the ComboBox so it decides if the currently selected Item is too big, and from code behind (or maybe a custom ComboBox Class) dynamically turn on/off the storyboard.




回答3:


First iterate through all items of your combobox, check for the width of every items by assigning the text to a label. Then, check width every time, if width of current item gets greater than previous items then change the maximum width.

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

OR

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

Thanks to those guys got it from here --> Auto-width of ComboBox's content



来源:https://stackoverflow.com/questions/45320802/sliding-text-in-combobox

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!