WPF Adding a Tooltip to the Track of a Slider

2019-05-14 19:34发布

问题:

I have added a tooltip (shown below) to the track in the slider template, but rather than binding to the current value of the slider, I would like to bind to the value corresponding to the "track value" the mouse is over. Similar to what the youtube video slider allows. So the user can mouseover the track and also see the corresponding value, without having to actually move the thumb.

<Track Grid.Row="1" Name="PART_Track" ToolTip="{Binding Path=Value}" ToolTipService.Placement="Mouse">
</Track>

Any ideas? Thanks!

回答1:

I have created an attached behaviour that will find the Track from a slider and subscribe to its MouseMove event to set the tooltip of the track to the corresponding value of the tick the mouse is over. I also added a Prefix property so you can write what the value is:

internal class ShowTickValueBehavior : Behavior<Slider>
{
    private Track track;

    public static readonly DependencyProperty PrefixProperty = DependencyProperty.Register(
        "Prefix",
        typeof(string),
        typeof(ShowTickValueBehavior),
        new PropertyMetadata(default(string)));

    public string Prefix
    {
        get
        {
            return (string)this.GetValue(PrefixProperty);
        }
        set
        {
            this.SetValue(PrefixProperty, value);
        }
    }

    protected override void OnAttached()
    {
        this.AssociatedObject.Loaded += this.AssociatedObjectOnLoaded;
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        this.track.MouseMove -= this.TrackOnMouseMove;
        this.track = null;
        base.OnDetaching();
    }

    private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        this.AssociatedObject.Loaded -= this.AssociatedObjectOnLoaded;
        this.track = (Track)this.AssociatedObject.Template.FindName("PART_Track", this.AssociatedObject);
        this.track.MouseMove += this.TrackOnMouseMove;
    }

    private void TrackOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
        var position = mouseEventArgs.GetPosition(this.track);
        var valueFromPoint = this.track.ValueFromPoint(position);
        var floorOfValueFromPoint = (int)Math.Floor(valueFromPoint);
        var toolTip = string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.Prefix, floorOfValueFromPoint);

        ToolTipService.SetToolTip(this.track, toolTip);
    }
}

Usage

<Window x:Class="TestSlider.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-TestSlider"
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    >
    <Grid>
        <Slider Name="Slider1"
                IsSnapToTickEnabled="True"
                TickFrequency="1"
                TickPlacement="BottomRight"
                IsMoveToPointEnabled="True"
                Minimum="13"
                Maximum="25"
                >
                <i:Interaction.Behaviors>
                    <local:ShowTickValueBehavior Prefix="Volume: "/>
                </i:Interaction.Behaviors>
        </Slider>
    </Grid>

Result:



回答2:

I'd imagine you're going to have to create a new control, inheriting from the slider. You'd need to implement mousein/out and mousemove, calculate the value based on mouse offset and change the tooltip.

I don't think there's any property you can use "out of the box" so the calculation may be rather tricky if you need to factor in reskinning of margins etc.



回答3:

First you need modify Slider control

    <Slider VerticalAlignment="Center" HorizontalAlignment="Stretch">
        <Slider.Template>
            <ControlTemplate TargetType="{x:Type Slider}">
                <Grid>
                    <xamlTest:ReflectablePopup x:Name="InfoPopup" Width="Auto" Height="Auto" PlacementTarget="{Binding ElementName=Thumb}" Placement="Top" StaysOpen="False" IsOpen="False" AllowsTransparency="True">
                        <Border Padding="2" CornerRadius="3" Background="#555C5C5C">
                            <Label Content="Your Text"></Label>
                        </Border>
                    </xamlTest:ReflectablePopup>
                    <Track x:Name="PART_Track">
                        <Track.Thumb>
                            <Thumb x:Name="Thumb" Width="10" Height="20">
                            </Thumb>
                        </Track.Thumb>
                    </Track>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger SourceName="Thumb" Property="IsDragging" Value="True">
                        <Setter Value="True" TargetName="InfoPopup" Property="IsOpen" />
                    </Trigger>
                    <Trigger SourceName="Thumb" Property="IsDragging" Value="False">
                        <Setter Value="False" TargetName="InfoPopup" Property="IsOpen" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Slider.Template>
    </Slider>

As you see, I using ReflectablePopup instead Popup. Because Popup can`t relocate after PlacementTarget moved.

Below the code fo ReflectablePopup (C#):

public class ReflectablePopup : Popup
{
    protected override void OnOpened(EventArgs e)
    {
        var friend = this.PlacementTarget;
        friend.QueryCursor += friend_QueryCursor;

        base.OnOpened(e);
    }

    protected override void OnClosed(EventArgs e)
    {
        var friend = this.PlacementTarget;
        friend.QueryCursor -= friend_QueryCursor;

        base.OnClosed(e);
    }

    private void friend_QueryCursor(object sender, System.Windows.Input.QueryCursorEventArgs e)
    {
        this.HorizontalOffset += +0.1;
        this.HorizontalOffset += -0.1;
    }
}