locked
How do you apply a converter to a Slider Auto-Tooltip? RRS feed

  • Question

  • I have a slider that has a min/max set in seconds.  I want to display the autotooltip as a timespan value  mm:ss.    

    I created a Sketchflow RangeSlider Usercontrol that has 2 thumbs with a LowerValue and UpperValue property.   using this example  

    I changed it a little so it would display like a sketchflow slider.

    Works great but I want to figure out how to apply a converter to the AutoToolTip?

    <UserControl
    	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"
    	mc:Ignorable="d"
    	xmlns:pc="http://schemas.microsoft.com/prototyping/2010/controls"
    	x:Class="RangeSlider"
    	x:Name="root">
    
    	<UserControl.Resources>
            <ControlTemplate x:Key="simpleSlider" TargetType="{x:Type Slider}">
                <Border SnapsToDevicePixels="true" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
    						                      
                        <Rectangle x:Name="PART_SelectionRange"/>
    
                        <Track x:Name="PART_Track" Grid.Row="1">
                            <Track.Thumb>
    				<Thumb x:Name="Thumb" Style="{StaticResource Thumb-Sketch}" Foreground="{TemplateBinding Foreground}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" MinHeight="20"/>
                            </Track.Thumb>
                        </Track>
                    </Grid>
                </Border>
            </ControlTemplate>
        </UserControl.Resources>
    	<Grid VerticalAlignment="Top">
    		<pc:SketchRectangleUC Margin="4,6,4,6" StrokeWidth="2" SegmentLength="8" SegmentOffset="0.1" SegmentVariance="0.1"/>
    	    <Slider x:Name="LowerSlider" Template="{StaticResource simpleSlider}" Style="{DynamicResource Slider-Sketch}" Minimum="{Binding ElementName=root, Path=Minimum}" Maximum="{Binding ElementName=root, Path=Maximum}" Value="{Binding ElementName=root, Path=LowerValue, Mode=TwoWay}" Margin="0,0,10,0" AutoToolTipPlacement="TopLeft"/>
    		<Slider x:Name="UpperSlider" Template="{StaticResource simpleSlider}" Style="{DynamicResource Slider-Sketch}" Minimum="{Binding ElementName=root, Path=Minimum}" Maximum="{Binding ElementName=root, Path=Maximum}" Value="{Binding ElementName=root, Path=UpperValue, Mode=TwoWay}" Margin="10,0,0,0" AutoToolTipPlacement="TopLeft"/>
    	</Grid>
    </UserControl>


    Jeff Davis

    Thursday, April 4, 2013 11:46 PM

Answers

  • The StringFormat '\{0\}Hz' doesn't help. The value just displays raw without being formatted.

    I'm pretty sure it is because the Property is an object and the binding being passed is and object so the stringformat doesn't get applied. It only applies if the what is

    being expected is different than what is being passed?

    I resolved this for now by creating a simple converter

    	Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
                    Return String.Format("{0} Hz",Cint(value))
    	End Function

    Just seems like a lot of extra work just to tag a "Hz" to the end of the tooltip?


    Jeff Davis

    • Marked as answer by JeffD503 Thursday, April 11, 2013 12:08 AM
    Tuesday, April 9, 2013 8:29 PM

All replies

  • Does this help? It is the first result for "autotooltip wpf"

    http://social.msdn.microsoft.com/forums/en-US/wpf/thread/9226ce5c-bd4d-441b-bedb-585cc57f8e49/

    I've never used those properties so I'm not very familiar with them.

    Friday, April 5, 2013 2:23 AM
    Moderator
  • Yea I read this one too.  I was trying to figure out a way to do this in my User Control XAML or class but it won't work unless you create a Slider Subclass to override those methods  Described here

    Jeff Davis

    Friday, April 5, 2013 4:10 PM
  • I have run his demo in Visual Studio 2012 and it works great.   When I try to implement this in Blend+Sketchflow it isn't working.

    I created a class module called FormattedSlider  with a namespace of FormattedSliderControl using his code.

    in my XAML for my user control I include the 

    xmlns:local="clr-namespace:FormattedSliderControl"

    and then I try to use the tag

     <local:FormattedSlider 

    But I get an error that says "The name "FormattedSlider" does not exist in the namespace "clr-namespace:FormattedSliderControl"

    What am I doing wrong?


    Jeff Davis

    Friday, April 5, 2013 5:37 PM
  • Did you do a build in blend after adding the code? Blend won't know about the new control types until you build. (Ctrl shift b)
    Friday, April 5, 2013 5:59 PM
    Moderator
  • Yes I did a build.  But once I type  <local:FormattedSlider ....   it says that the name does not exist in the namespace and any further builds fail.  I remove the tag and revert back to <Slider ...  and it builds even with the namespace reference in the XAML  and the class defined in the project.  All the code seems to be correct but I can't refer to the custom slider object??

    I tried this in Visual Studio 2012 and get the same error.  

    All I am trying to do is format the AutoToolTip output of the thumb of  two sliders in my Sketchflow RangeSlider user control.  I want to be able to bind a ValueConverter to the tooltip for each thumb so I can display custom formated text above the thumb instead of just the raw slider value.


    Jeff Davis

    Friday, April 5, 2013 6:30 PM
  • xmlns:local="clr-namespace:FormattedSliderControl"

    Does FormattedSliderControl match the namespace of the file you put the code in?

    Can you find it in the asset pane in the project section and drag it to the artboard?

    Friday, April 5, 2013 6:41 PM
    Moderator
  • I opened up the source in reflector for slider, it looks like the value of the autotooltip can't be changed/formatted. You can see this if you open Slider.GetAutoToolTipNumber() in reflector or ilspy, it is hard coded to use the number with the number of decimal points you specify with AutoToolTipPrecision.

    The technique described in that blog post is probably the easiest way to accomplish what you want. More complex ways would have you adding your own tooltip or popup and managing its state using the begin/end drag events like the slider does itself.

    Friday, April 5, 2013 7:09 PM
    Moderator
  • Great suggestion.   Yes it was in the Asset pane.

    The syntax for the XAML was

    <FormattedSliderControl:FormattedSlider ....    no local: needed.

    It works great!


    Jeff Davis

    Friday, April 5, 2013 7:52 PM
  • More complications ensue....

    So now I am trying to modify the example code that updates the AutoTooltip to use a converter.  I have one called TimeSpanConverter that takes a value and converts it into a string  in the form mm:ss  so a value of 85 would be  "01:15".    

    So I want to be able to do something like this....

    <local:FormattedSlider  AutoToolTipPlacement="TopLeft" AutoToolTipFormat="{Binding Value, Converter={local:TimeSpanConverter}}" />

    I know I need to convert the AutoToolTipFormat property to be a dependency property so I added this to the class

    Public Shared ReadOnly AutoToolTipFormatProperty As DependencyProperty = DependencyProperty.Register("AutoToolTipFormat", GetType(String), GetType(FormattedSlider))

    But I left the Public Property the same.   But this must not be right because I am just getting the value back and not the formatted version?

    Public Property AutoToolTipFormat() As String
    Get
    Return _autoToolTipFormat
    End Get
    Set
    _autoToolTipFormat = value
    End Set
    End Property

    I am stretching my WPF/Blend+Sketchflow knowledge/experience here...what am I missing?


    Jeff Davis

    Friday, April 5, 2013 8:35 PM
  • Hello Jeff and Chuck.

    Forgive the intrusion, but if I may suggest something...

    First set the AutoToolTipPlacement to None.

    Select the slider > Edit Template > Edit a Copy.

    Select the Thumb in the slider style/template... > Edit Template > Edit a copy

    It's setup will be a canvas with three paths - Background, InnerBorder, OuterBorder.

    Add a TextBlock to the canvas and place it over the paths....

    <TextBlock x:Name="tb" TextWrapping="NoWrap" Text="{Binding Value, Converter={StaticResource MyConverter}, RelativeSource={RelativeSource AncestorType={x:Type Slider}}, UpdateSourceTrigger=PropertyChanged}" Margin="0" MinWidth="20" TextAlignment="Right" HorizontalAlignment="Right" VerticalAlignment="Top" Background="White" Canvas.Top="-30" Visibility="Hidden"/>
    
    

    For my sample I used a TextBlock... Make the tooltip look however you want...

    Set it up with your converter.

    Make the TextBlock Hidden.

    Then in the Triggers Panel Select the "IsMouseOver" Trigger and simply set the visibility of the TextBlock to Visible while it is in record mode.  Blend will take care of the xaml.

    Build and test.

    If you are developing for a Windows 8 project the "ThumbToolTipValueConverter" will be available to you for the actual project, that you don't have to go through all the above.

    But it just seems to be consuming a lot of your time that might be best served elsewhere, just to make something work on a demo project.

    Again sorry for the intrusion.

    ~Christine

    Friday, April 5, 2013 11:20 PM
  • The way you are trying to use that property won't work for what you are trying to do, you are trying to set the value of the tooltip, but that property is for the format string. I spent some time and rewrote the example from that blog post to do what you want, here is the code and a xaml example of it being used, you should be able to adapt this to work with what you have:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Controls;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Data;
    
    namespace FormattedSlider
    {
        public class FormattedSlider : Slider
        {
            private ToolTip _autoToolTip;
    
            public static readonly DependencyProperty ToolTipContentProperty = DependencyProperty.Register("ToolTipContent", typeof(object), typeof(FormattedSlider), new PropertyMetadata(ToolTipContentUpdated));
    
            public static void ToolTipContentUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                FormattedSlider fs = (FormattedSlider)d;
                fs.UpdateToolTip();
            }
    
            public object ToolTipContent
            {
                get
                {
                    return this.GetValue(ToolTipContentProperty);
                }
                set
                {
                    this.SetValue(ToolTipContentProperty, value);
                }
            }
    
            protected override void OnThumbDragStarted(System.Windows.Controls.Primitives.DragStartedEventArgs e)
            {
                base.OnThumbDragStarted(e);
                this.UpdateToolTip();
            }
    
            protected override void OnThumbDragDelta(System.Windows.Controls.Primitives.DragDeltaEventArgs e)
            {
                base.OnThumbDragDelta(e);
                this.UpdateToolTip();
            }
    
            private void UpdateToolTip()
            {
                if (this.AutoToolTip != null)
                {
                    this.AutoToolTip.Content = this.ToolTipContent;
                }
            }
    
            private ToolTip AutoToolTip
            {
                get
                {
                    if (this._autoToolTip == null)
                    {
                        FieldInfo field = typeof(Slider).GetField("_autoToolTip", BindingFlags.NonPublic | BindingFlags.Instance);
                        this._autoToolTip = field.GetValue(this) as ToolTip;
                    }
                    return this._autoToolTip;
                }
            }
        }
    
        public class IntToTimeSpanConverter : IValueConverter
        {
    
    
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                double? d = value as double?;
                if (d.HasValue)
                {
                    return TimeSpan.FromSeconds(d.Value).ToString("mm\\:ss");
                }
    
                return value;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return value;
            }
        }
    }
    

    <Window
    	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    	xmlns:FormattedSlider="clr-namespace:FormattedSlider"
    	x:Class="WpfApplication5.MainWindow"
    	x:Name="Window"
    	Title="MainWindow"
    	Width="640" Height="480">
    	<Window.Resources>
    		<FormattedSlider:IntToTimeSpanConverter x:Key="IntToTimeSpanConverter"/>
    	</Window.Resources>
    
    	<Grid x:Name="LayoutRoot">
    		<FormattedSlider:FormattedSlider x:Name="formattedSlider" Margin="85,125,115,0" VerticalAlignment="Top" Height="36" AutoToolTipPlacement="TopLeft" ToolTipContent="{Binding Value, Converter={StaticResource IntToTimeSpanConverter}, ElementName=formattedSlider}" Maximum="500"/>
    	</Grid>
    </Window>

    Saturday, April 6, 2013 7:48 PM
    Moderator
  • Thanks Chuck that helps.   Now only one last thing to bring this all together.

    I want to use this FormattedSlider tooltip formated slider inside of a user control that uses 2 sliders....something like this....

    	<Grid VerticalAlignment="Top">
    		<pc:SketchRectangleUC Margin="4,6,4,6" StrokeWidth="2" SegmentLength="8" SegmentOffset="0.1" SegmentVariance="0.1"/>
    		<local:FormattedSlider x:Name="LowerSlider" Template="{StaticResource simpleSlider}" Style="{DynamicResource Slider-Sketch}" Minimum="{Binding Minimum, ElementName=root}" Maximum="{Binding Maximum, ElementName=root}" Value="{Binding LowerValue, ElementName=root, Mode=TwoWay}" Margin="0,0,10,0" AutoToolTipPlacement="TopLeft"/>
    		<local:FormattedSlider x:Name="UpperSlider" Template="{StaticResource simpleSlider}" Style="{DynamicResource Slider-Sketch}" Minimum="{Binding Minimum, ElementName=root}" Maximum="{Binding Maximum, ElementName=root}" Value="{Binding UpperValue, ElementName=root, Mode=TwoWay}" Margin="10,0,0,0" AutoToolTipPlacement="TopLeft"/>
    	</Grid>

    So I added this to the code in the User Control to handle the 2 formatted strings

    Public Shared ReadOnly LowerToolTipContentProperty As DependencyProperty = DependencyProperty.Register("LowerToolTipContent", GetType(Object), GetType(RangeSlider))
    
    Public Property LowerToolTipContent() As Object
    	Get
    		Return Me.GetValue(LowerToolTipContentProperty)
    	End Get
    	Set
    		Me.SetValue(LowerToolTipContentProperty, value)
    	End Set
    End Property
    
    Public Shared ReadOnly UpperToolTipContentProperty As DependencyProperty = DependencyProperty.Register("UpperToolTipContent", GetType(Object), GetType(RangeSlider))
    
    Public Property UpperToolTipContent() As Object
    	Get
    		Return Me.GetValue(UpperToolTipContentProperty)
    	End Get
    	Set
    		Me.SetValue(UpperToolTipContentProperty, value)
    	End Set
    End Property

    My actual XAML looks like this....

    <local:RangeSlider x:Name="LoopRange" LowerValue="30" Minimum="0" UpperValue="120" Maximum="240" LowerToolTipContent="{Binding LowerValue, Converter={local:TimeSpanConverter}, ElementName=LoopRange}" UpperToolTipContent="{Binding UpperValue, Converter={local:TimeSpanConverter}, ElementName=LoopRange}"/>
    

    But this isn't working?  I get a blank tooltip when moving the sliders.   I must be missing something in the Code for the User Control?  But I figured if I just passed the converter on it would work?

    jeff


    Jeff Davis

    Monday, April 8, 2013 4:58 PM
  • The xaml that declares the formatted sliders never references the tooltip properties you defined, as far as I can see.
    Monday, April 8, 2013 5:11 PM
    Moderator
  • OK one step forward but still not there yet.

    	<Grid VerticalAlignment="Top">
    		<pc:SketchRectangleUC Margin="4,6,4,6" StrokeWidth="2" SegmentLength="8" SegmentOffset="0.1" SegmentVariance="0.1"/>
    		<local:FormattedSlider x:Name="LowerSlider" Template="{StaticResource simpleSlider}" Style="{DynamicResource Slider-Sketch}" Minimum="{Binding Minimum, ElementName=root}" Maximum="{Binding Maximum, ElementName=root}" Value="{Binding LowerValue, ElementName=root, Mode=TwoWay}" Margin="0,0,10,0" AutoToolTipPlacement="TopLeft" ToolTipContent="{Binding LowerToolTipContent, ElementName=root}" />
    		<local:FormattedSlider x:Name="UpperSlider" Template="{StaticResource simpleSlider}" Style="{DynamicResource Slider-Sketch}" Minimum="{Binding Minimum, ElementName=root}" Maximum="{Binding Maximum, ElementName=root}" Value="{Binding UpperValue, ElementName=root, Mode=TwoWay}" Margin="10,0,0,0" AutoToolTipPlacement="TopLeft" ToolTipContent="{Binding UpperToolTipContent, ElementName=root}"/>
    	</Grid>

    I added the ToolTipContent binding to the user control and now the data is displayed just fine.  I can pass a converter like this and it works great!

    <local:RangeSlider x:Name="LoopRange" Grid.ColumnSpan="3" Margin="8,434.04,23,65" LowerValue="30" Minimum="0" UpperValue="120" Maximum="240" LowerToolTipContent="{Binding LowerValue, Converter={local:TimeSpanConverter}, ElementName=LoopRange}" UpperToolTipContent="{Binding UpperValue, Converter={local:TimeSpanConverter}, ElementName=LoopRange}"/>
    

    But now the problem is that I want to be able to display the value in the tooltip sometimes without a converter.   For example I want to use this control for a Hz range from 0 to 20000.   I want to display the value in whole numbers as "3215Hz"  in the tooltip and I really don't want to have to write code just to tag "Hz" onto the end of the tooltip value for the slider.

    But  doing just this only displays the raw value of the slider....

    <local:RangeSlider x:Name="SpeakerCalRange" LowerValue="{Binding SpeakerCalMin}" Minimum="0" UpperValue="{Binding SpeakerCalMax}" Maximum="20000" Grid.Column="1" LowerToolTipContent="{Binding LowerValue, ElementName=SpeakerCalRange}" UpperToolTipContent="{Binding UpperValue, ElementName=SpeakerCalRange}"/>
    

    When I use a StringFormat it displays nothing?

    <local:RangeSlider x:Name="SpeakerCalRange"  LowerValue="{Binding SpeakerCalMin}" Maximum="20000" Minimum="0" UpperValue="{Binding SpeakerCalMax}" LowerToolTipContent="{Binding LowerValue,StringFormat='{}{0}Hz'}" UpperToolTipContent="{Binding UpperValue,StringFormat='{}{0}Hz'}" />
    

    I've read about how StringFormat only works if the value is not the same but since my ToolTip is an object and the value being passed is object I don't think the StringFormat isn't working.   I can't use ContentStringFormat because there is no Content property of the tooltip.   

    Not sure how to fix this or what to do?  I REALLy don't want to have to write a converter to just append the "Hz"



    Jeff Davis

    Tuesday, April 9, 2013 4:09 AM
  • I don't know why that isn't working, I would run it in VS and look for binding errors, and put a breakpoint in the tooltip update code and see what the values are.

    Also, the converter would be so simple, that really is the right answer if the string formatting doesn't work.

    Tuesday, April 9, 2013 12:49 PM
    Moderator
  • Might help to try the stringformat a bit differently;


    LowerToolTipContent="{Binding LowerValue, StringFormat='\{0\}Hz'}"

    Tuesday, April 9, 2013 7:21 PM
  • The StringFormat '\{0\}Hz' doesn't help. The value just displays raw without being formatted.

    I'm pretty sure it is because the Property is an object and the binding being passed is and object so the stringformat doesn't get applied. It only applies if the what is

    being expected is different than what is being passed?

    I resolved this for now by creating a simple converter

    	Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
                    Return String.Format("{0} Hz",Cint(value))
    	End Function

    Just seems like a lot of extra work just to tag a "Hz" to the end of the tooltip?


    Jeff Davis

    • Marked as answer by JeffD503 Thursday, April 11, 2013 12:08 AM
    Tuesday, April 9, 2013 8:29 PM