mjgalindo
mjgalindo

Reputation: 876

WPF variable ammount of elements

I'm creating my own slider template and style. It's similar to the basic slider but the track bar contains circles (Ellipses) with a proper separation. The number of circles in the bar should be such that the slider thumb will stop exactly over them.

In a quick approximation, there should be Slider.Maximum - Slider.Minimum / StepSize Ellipses in the control. But, to my knowledge, there is no way to specify a variable number of anything from xaml, is there?

This is, in summary, the code that represents what I'd want to achieve:

<ControlTemplate x:Key="SliderHorizontal" TargetType="{x:Type Slider}">
        <Grid Height="4">
            <Border x:Name="TrackBackground">
                <Rectangle x:Name="PART_SelectionRange" Fill="{StaticResource SliderThumb.Track.BackgroundSelected}" 
                           HorizontalAlignment="Left" Margin="0 0 16 0" Visibility="Hidden"/>
            </Border>
            <Grid>
                <!-- Have a variable ammount of column definitions and ellipses -->
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="4"/>
                    <ColumnDefinition Width="*"/>
                    ...
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="4"/>
                </Grid.ColumnDefinitions>
                <Ellipse Grid.Column="0" Fill="White"/>
                ...
                <Ellipse Grid.Column="X" Fill="White"/>
            </Grid>
            <Track x:Name="PART_Track">
                <Track.Thumb>
                    ...
                </Track.Thumb>
            </Track>
        </Grid>
        <ControlTemplate.Triggers>
            ...
        </ControlTemplate.Triggers>
    </ControlTemplate>

My code so far allows me to use a fixed ammount of ellipses, which looks very bad when it doesn't match the values that are represented by a slider.

I'm new to WPF so I don't know what doing this from code-behind would entail. Can I simply create a class inheriting from Slider and add the Ellipses there? If so, could I get a simple example doing it?

Upvotes: 0

Views: 84

Answers (1)

An ItemsControl can display a variable amount of something. Bind its ItemsSource property to a collection of the things you want to display, and use its ItemTemplate property to determine how they're displayed.

Here's a rough implementation that you should be able to fine tune. In particular, TickConverter should be IMultiValueConverter, with Minimum, Maximum, and TickFrequency bound separately via a MultiBinding -- that way, it'll get automagically reinvoked when any of those properties changes.

public class TickConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var slider = (Slider)value;

        var tickOffsets = new List<double>();

        var sliderRange = (slider.Maximum - slider.Minimum);

        var tickcount = (int)Math.Floor(sliderRange / slider.TickFrequency);

        return Enumerable.Range(0, tickcount);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Template:

<ControlTemplate TargetType="{x:Type Slider}" x:Key="SliderHorizontal">
    <ControlTemplate.Resources>
        <local:TickConverter x:Key="TickConverter" />
    </ControlTemplate.Resources>
    <Grid Height="4">
        <Border x:Name="TrackBackground">
            <Rectangle 
                x:Name="PART_SelectionRange"
                HorizontalAlignment="Left" 
                Margin="0 0 16 0" 
                Visibility="Hidden"
                />
        </Border>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <ItemsControl
                Grid.Column="0"
                ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TickConverter}}"
                >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <!-- UniformGrid spaces items out evenly -->
                        <UniformGrid 
                            Rows="1" 
                            />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <!-- Align left to fill in start tick for each interval -->
                        <Ellipse
                            HorizontalAlignment="Left"
                            Fill="DeepSkyBlue"
                            Width="3"
                            Height="4"
                            />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
            <!-- And add the end tick -->
            <Ellipse
                Grid.Column="1"
                HorizontalAlignment="Left"
                Fill="DeepSkyBlue"
                Width="3"
                Height="4"
                />
        </Grid>
        <!-- 
        I don't know what you were doing with the thumb, but you didn't ask about it
        so I just ignored it. 
        -->
        <Track x:Name="PART_Track" />
    </Grid>
</ControlTemplate>

Upvotes: 3

Related Questions