Sliding text in combobox

I would like to copy the text (text selection) of the selected item into the combo box if its length is greater than the width of the dropdown. It can either be automatic or when the user hovers over the combo box. The problem is I don't know how to do it. Perhaps it is possible to do this with a render transformation (previous definition of a text block inside it)? or with a storyboard?

Here is the xaml I need to change

<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 I don't know what properties should be used in the storyboard

EDIT2: I took the combo box template and changed the textbox 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 it doesn't seem to have an effect

EDIT 3: I realized that I had to use a template that uses the style 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 section of the textbox is the one that uses the style. However, when I do Template="{StaticResource ComboBoxEditableTemplate}"

in the combobox, I no longer see the selected item, even if it is not null

+3


source to share


3 answers


Updated 08/02

Basically, to avoid the animation problems you face - you need to add ScrollViewer

as a wrapper to your content site or select box TextBlock

. You can do this using a custom template selector for yours ComboBox

, as defined below, or provide a custom element template with ScollViewer

.

Second, you can create your own control ComboBox

in C # to bind this template-selector / template-element and apply slide animation to MouseEnterEvent

; or implement the same in XAML.

This example implements control in C # and provides a usage example in XAML.

Custom Control Code 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:

sample output

<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 the SlideForever property to control the RepeatBehaviour.

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

      

+1


source


I have looked into this and I think I have your solution. You have to concatenate both RenderTransform

and Storyboard

in ComboBox

ContentPresenter

(this shows 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 a ComboBox of size 200, scrolling from 0 to 200, then from -200 to 0, scrolls the text on the right side of the control and on the left. (If you like, you can reset the 2nd DoubleAnimation

and set it AutoReverse

to True to get the text back in if you prefer that. This won't tell you items that are too large for the control, you will need to write some code for the ComboBox to get it decided that the currently selected item is too large, and from code (or perhaps a custom ComboBox class) dynamically turns on / off the storyboard.

+1


source


First iterate over all the elements of your combo box, check the width of each element by assigning text to the label. Then check the width each time, if the width of the current element becomes larger than the previous ones, 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 these guys, he got it from here -> Auto-Width ComboBox Content

-2


source







All Articles