New Custom Slider for Silverlight 2 media players

Sign in to queue

Description

My Silverlight teammates Akshay Johar and Andre Michaud have built a new custom slider for use in Silverlight 2 media players.

When you scrub the default slider in a MediaElement today, it generates a whole slew of valueChanged events, resulting in a huge number of seeks per second. This can confuse media playback, particularly when streaming from Windows Media Services, which then gets flooded with many requests a second.

Essentially it detects a “crazy mode” scrub, and quietly ignores it while the “crazy mode” is in place. Once out of this mode, it happily sends and receives a successful seek.

So, the player only fires ValueChanged on mouse up (on the slider thumb or the slider tracker), or the final seek request of a cluster that comes in at once.

It’s also uses Tim Heuer’s absolute value slider.

Now, let me try this new Windows Live Writer plug-in for Insert Code Snippet for the C# code.

(EDIT) A sample project is also now available for download.

 1: using System;
 2: using System.Net;
 3: using System.Windows;
 4: using System.Windows.Controls;
 5: using System.Windows.Documents;
 6: using System.Windows.Ink;
 7: using System.Windows.Input;
 8: using System.Windows.Media;
 9: using System.Windows.Media.Animation;
 10: using System.Windows.Shapes;
 11: using System.Windows.Controls.Primitives;
 12: using System.Windows.Threading;
 13:  
 14: namespace VirtualizedSlider
 15: {
 16:     public class CustomSlider : Slider
 17:     {
 18:         public Thumb horizontalThumb;
 19:         private FrameworkElement horizontalLeftTrack;
 20:         private FrameworkElement horizontalRightTrack;
 21:         private double oldValue = 0, newValue = 0, prevNewValue = 0;
 22:         public event RoutedPropertyChangedEventHandler<double> MyValueChanged;
 23:         public event RoutedPropertyChangedEventHandler<double> MyValueChangedInDrag;
 24:         
 25:         private DispatcherTimer dragtimer = new DispatcherTimer();
 26:         private double dragTimeElapsed = 0;
 27:         private const short DragWaitThreshold = 200, DragWaitInterval = 100;
 28:         public Rectangle progressRect = null;
 29:         private bool dragSeekJustFired = false;
 30:  
 31:         public CustomSlider()
 32:         {
 33:             this.ValueChanged += new RoutedPropertyChangedEventHandler<double>(CustomSlider_ValueChanged);
 34:             dragtimer.Interval = new TimeSpan(0, 0, 0, 0, DragWaitInterval);
 35:             dragtimer.Tick += new EventHandler(dragtimer_Tick);
 36:             
 37:         }
 38:  
 39:         void dragtimer_Tick(object sender, EventArgs e)
 40:         {
 41:             dragTimeElapsed += DragWaitInterval;
 42:             if (dragTimeElapsed >= DragWaitThreshold)
 43:             {
 44:                 RoutedPropertyChangedEventHandler<double> handler = MyValueChangedInDrag;
 45:                 if ((handler != null) && (newValue != prevNewValue))
 46:                 {
 47:                     handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, newValue));
 48:                     dragSeekJustFired = true;
 49:                     prevNewValue = newValue;
 50:                 }
 51:                 dragTimeElapsed = 0;
 52:             }
 53:         }
 54:  
 55:         void CustomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
 56:         {
 57:             oldValue = e.OldValue;
 58:             newValue = e.NewValue;
 59:             if (horizontalThumb.IsDragging)
 60:             {
 61:                 dragTimeElapsed = 0;
 62:                 dragtimer.Stop();
 63:                 dragtimer.Start();
 64:                 dragSeekJustFired = false;
 65:             }
 66:         }
 67:  
 68:         public override void OnApplyTemplate()
 69:         {
 70:             base.OnApplyTemplate();
 71:  
 72:             horizontalThumb = GetTemplateChild("HorizontalThumb") as Thumb;
 73:  
 74:             horizontalLeftTrack = GetTemplateChild("LeftTrack") as FrameworkElement;
 75:             horizontalRightTrack = GetTemplateChild("RightTrack") as FrameworkElement;
 76:             progressRect = GetTemplateChild("progressRect") as Rectangle;
 77:  
 78:             if (horizontalLeftTrack != null) horizontalLeftTrack.MouseLeftButtonDown += new MouseButtonEventHandler(OnMoveThumbToMouse);
 79:             if (horizontalRightTrack != null) horizontalRightTrack.MouseLeftButtonDown += new MouseButtonEventHandler(OnMoveThumbToMouse);
 80:  
 81:             horizontalThumb.DragCompleted += new DragCompletedEventHandler(DragCompleted);
 82:             progressRect.Width = this.Width;            
 83:         }
 84:  
 85:         private void OnMoveThumbToMouse(object sender, MouseButtonEventArgs e)
 86:         {
 87:             e.Handled = true;
 88:             Point p = e.GetPosition(this);
 89:  
 90:             if (this.Orientation == Orientation.Horizontal)
 91:             {
 92:                 Value = (p.X - (horizontalThumb.ActualWidth / 2)) / (ActualWidth - horizontalThumb.ActualWidth) * Maximum;
 93:             }
 94:             RoutedPropertyChangedEventHandler<double> handler = MyValueChanged;
 95:             if (handler != null)
 96:             {
 97:                 handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, Value));
 98:             }
 99:         }
 100:  
 101:         private void DragCompleted(object sender, DragCompletedEventArgs e)
 102:         {
 103:             dragtimer.Stop();
 104:             dragTimeElapsed = 0;
 105:             RoutedPropertyChangedEventHandler<double> handler = MyValueChanged;
 106:             if ((handler != null) && (!dragSeekJustFired))
 107:             {
 108:                 handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, this.Value));
 109:             }
 110:         }
 111:     }
 112: }

 

And here’s the XAML from the progressSliderStyle, demonstrating how the custom slider knows about mouse clicks on the track (the “LeftTrack” and “RightTrack” rectangles inserted in the template handle this):

 1: <Setter Property="Template">
 2:                  <Setter.Value>
 3:                      <ControlTemplate TargetType="Slider">
 4:                          <Grid x:Name="Root">
 5:                              <Grid.Resources>
 6:                                  <ControlTemplate x:Key="RepeatButtonTemplate">
 7:                                      <Grid x:Name="Root" Opacity="0" Background="Transparent"/>
 8:                                  </ControlTemplate>
 9:                              </Grid.Resources>
 10:                              <vsm:VisualStateManager.VisualStateGroups>
 11:                                  <vsm:VisualStateGroup x:Name="CommonStates">
 12:                                      <vsm:VisualState x:Name="Normal"/>
 13:                                      <vsm:VisualState x:Name="MouseOver"/>
 14:                                      <vsm:VisualState x:Name="Disabled">
 15:                                          <Storyboard>
 16:                                              <DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="(UIElement.Opacity)" To="0.5"/>
 17:                                          </Storyboard>
 18:                                      </vsm:VisualState>
 19:                                  </vsm:VisualStateGroup>
 20:                              </vsm:VisualStateManager.VisualStateGroups>
 21:  
 22:                              <!-- Horizontal Template -->
 23:                              <Grid x:Name="HorizontalTemplate" Background="{TemplateBinding Background}">
 24:                                  <Grid.ColumnDefinitions>
 25:                                      <ColumnDefinition Width="Auto"/>
 26:                                      <ColumnDefinition Width="Auto"/>
 27:                                      <ColumnDefinition Width="*"/>
 28:                                  </Grid.ColumnDefinitions>
 29:  
 30:                                  <!-- Track Layer -->
 31:                                  <Rectangle x:Name="progressRect" Stroke="#FFA3AEB9" StrokeThickness="{TemplateBinding BorderThickness}" Fill="Red" Grid.Column="0" Grid.ColumnSpan="3" Height="3" Width="5" RadiusX="1" RadiusY="1" Margin="5,0,5,0" Canvas.ZIndex="1" Visibility="Collapsed" HorizontalAlignment="Left" />
 32:                                  <Rectangle Stroke="#FFA3AEB9" StrokeThickness="{TemplateBinding BorderThickness}" Fill="#FFE6EFF7" Grid.Column="0" Grid.ColumnSpan="3" Height="3" RadiusX="1" RadiusY="1" Margin="5,0,5,0"  Canvas.ZIndex="0" />
 33:  
 34:                                  <!-- Repeat Buttons + Thumb -->
 35:                                  <RepeatButton x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton" IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}" Grid.Column="0" Canvas.ZIndex="1"/>
 36:                                  <Rectangle x:Name="LeftTrack" Grid.Column="0" Fill="#00FFFFFF" Cursor="Hand" Canvas.ZIndex="1"/>
 37:                                  <Thumb Height="18" x:Name="HorizontalThumb" Width="11" Grid.Column="1" IsTabStop="True" Canvas.ZIndex="1"/>
 38:                                  <RepeatButton x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton" IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}" Grid.Column="2" Canvas.ZIndex="1"/>
 39:                                  <Rectangle x:Name="RightTrack" Grid.Column="2" Fill="#00FFFFFF" Cursor="Hand" Canvas.ZIndex="1"/>
 40:                              </Grid>
 41:                          </Grid>
 42:                      </ControlTemplate>
 43:                  </Setter.Value>
 44:              </Setter>

Tags:

C#, Silverlight, XAML

The Discussion

  • User profile image
    abner

    Hello Ben,

    This is Rick over at uvntv.com

    Can you make this code available for download

    Thanks

  • User profile image
    abner

    Hello Ben,

    This is Rick over at uvntv.com

    Can you make this code available for download

    Thanks

Add Your 2 Cents