Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Thursday, 19 May 2011

WPF/Silverlight Content Ticker/News Ticker Control

WPF includes the Canvas control that allows you to place elements using exact coordinates. To position an element on the Canvas, you set the attached Canvas.Left and Canvas.Top properties. Canvas.Left sets the number of units between the left edge of your element and the left edge of the Canvas. Canvas.Top sets the number of units between the top of your element and the top of the Canvas. Although the Canvas control has a definite visible area determined by the Height and Width properties of the control, it allows child controls to be virtually placed at any coordinates. We can use this particular feature of Canvas control to achieve the ticking/sliding effect by continuously changing the coordinates of the content.
Demo application screenshot.
ContentTicker control derives from WPF ContentControl. The ControlTemplate is defined to place the content in a Canvas control. Also a double animation is defined that is started on loading of the control. The animation target property is set to the attached property (Canvas.Left) of the content.

    public class ContentTicker : ContentControl
    {
        Storyboard _ContentTickerStoryboard = null;
        Canvas _ContentControl = null;
        ContentPresenter _Content = null;

        static ContentTicker()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ContentTicker), new FrameworkPropertyMetadata(typeof(ContentTicker)));
        }

        public ContentTicker()
        {
            this.Loaded += new RoutedEventHandler(ContentTicker_Loaded);
        }

        ..
    }


        
            
                
                    
                        
                            
                        
                    
                
            
            
        
    

The ContentTicker control defines two additional dependency properties for defining the rate (speed) of ticking and direction (east or west). The rate is used to calculate the duration of the animation, the time required to complete one cycle of the animation.

        public double Rate
        {
            get { return (double)GetValue(RateProperty); }
            set { SetValue(RateProperty, value); }
        }

        public static readonly DependencyProperty RateProperty =
            DependencyProperty.Register("Rate", typeof(double), typeof(ContentTicker), new UIPropertyMetadata(60.0));

        public TickerDirection Direction
        {
            get { return (TickerDirection)GetValue(DirectionProperty); }
            set { SetValue(DirectionProperty, value); }
        }

        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(TickerDirection), typeof(ContentTicker), new UIPropertyMetadata(TickerDirection.West));

The Canvas size is adjusted while loading the control and Content is vertically aligned in the canvas according to the user specified settings. Further the animation parameters are adjusted according to the Width of Canvas and Content controls. The animation is dependent on the Width of the Canvas and the content so the animation details are updated every time the size of these are changed.

        void UpdateAnimationDetails(double holderLength, double contentLength)
        {
            DoubleAnimation animation = 
                _ContentTickerStoryboard.Children.First() as DoubleAnimation;
            if (animation != null)
            {
                bool start = false;
                if (IsStarted)
                {
                    Stop();
                    start = true;
                }

                double from = 0, to = 0, time = 0;
                switch (Direction)
                {
                    case TickerDirection.West:
                        from = holderLength;
                        to = -1 * contentLength;
                        time = from / Rate;
                        break;
                    case TickerDirection.East:
                        from = -1 * contentLength;
                        to = holderLength;
                        time = to / Rate;
                        break;
                }

                animation.From = from;
                animation.To = to;
                TimeSpan newDuration = TimeSpan.FromSeconds(time);
                animation.Duration = new Duration(newDuration);

                if (start)
                {
                    TimeSpan? oldDuration = null;
                    if (animation.Duration.HasTimeSpan)
                        oldDuration = animation.Duration.TimeSpan;
                    TimeSpan? currentTime = _ContentTickerStoryboard.GetCurrentTime(_ContentControl);
                    int? iteration = _ContentTickerStoryboard.GetCurrentIteration(_ContentControl);
                    TimeSpan? offset = 
                        TimeSpan.FromSeconds(
                        currentTime.HasValue ? 
                        currentTime.Value.TotalSeconds % (oldDuration.HasValue ? oldDuration.Value.TotalSeconds : 1.0) : 
                        0.0);
                    
                    Start();

                    if (offset.HasValue &&
                        offset.Value != TimeSpan.Zero &&
                        offset.Value < newDuration)
                        _ContentTickerStoryboard.SeekAlignedToLastTick(_ContentControl, offset.Value, TimeSeekOrigin.BeginTime);
                }
            }
        }

The ContentTicker control is generic control to slide the content. It can be used as a news ticker, thumbnails slider, RSS feed slider etc., depending on the requirement. The Start/Stop and Pause/Resume methods can be used to dynamically change the behavior of the sliding content. The demo application uses the control as a text ticker and provides the interfaces to change the speed, content and direction at run time.

Download Source

Thursday, 21 April 2011

WPF / Silverlight Countdown Timer and Time Ticker TextBlock

In one of my applications I had to display a timer for all the items in a ListView, i.e., given the last update time of an item I required to display the time elapsed since the update time and update it after every second. In a classic application the solution would have been to add a timer control to the host form and update each elapsed time value at each tick (second) of the timer.

However in WPF/Silverlight  we can easily achieve this by creating a custom control inheriting it from the TextBlock control. The custom control defines a TimeSpan property that holds the time elapsed (or time remaining in case of count down) and binds it to the TextBlock.Text property. The TimeSpan is updated every second and thus the Text property displays the updated value.


Image: Countdown Timer and Time Ticker Demo using TimerTextBlock control.

The control uses System.Threading.Timer object for providing updates. The control initializes a static Timer object with  an interval of one second and the TimerCallback delegate that is raised after the provided interval. Also the control defines a private static event OnTick that is raised in the callback method.

        private static event EventHandler OnTick;
        private static Timer _UpdateTimer = new Timer(new TimerCallback(UpdateTimer), null, 1000, 1000);

        private static void UpdateTimer(object state)
        {
            EventHandler onTick = OnTick;
            if (onTick != null)
                onTick(null, EventArgs.Empty);
        }

The control subscribes the OnTick event while loading to receive the event and unsubscribes the event while unloading. Also the control binds the TimeSpan property with TextBlock.Text property. Thus the TextBlock.Text property displays the updated TimeSpan value formatted according to the provide TimeFormat.

        private void Init()
        {
            Loaded += new RoutedEventHandler(TimerTextBlock_Loaded);
            Unloaded += new RoutedEventHandler(TimerTextBlock_Unloaded);
        }


        void TimerTextBlock_Loaded(object sender, RoutedEventArgs e)
        {
            Binding binding = new Binding("TimeSpan");
            binding.Source = this;
            binding.Mode = BindingMode.OneWay;
            binding.StringFormat = TimeFormat;

            SetBinding(TextProperty, binding);           

            _UpdateTimeInvoker = new Invoker(UpdateTime);

            OnTick += new EventHandler(TimerTextBlock_OnTick);
        }

        void TimerTextBlock_Unloaded(object sender, RoutedEventArgs e)
        {
            OnTick -= new EventHandler(TimerTextBlock_OnTick);
        }


The TimeSpan is updated in the OnTick event handler.

        void TimerTextBlock_OnTick(object sender, EventArgs e)
        {
            Dispatcher.Invoke(_UpdateTimeInvoker);
        }

        private void UpdateTime()
        {
            if (IsStarted)
            {
                TimeSpan step = TimeSpan.FromSeconds(1);
                if (IsCountDown)
                {
                    if (TimeSpan >= TimeSpan.FromSeconds(1))
                    {
                        TimeSpan -= step;
                        if (TimeSpan.TotalSeconds <= 0)
                        {
                            TimeSpan = TimeSpan.Zero;
                            IsStarted = false;
                            NotifyCountDownComplete();
                        }
                    }
                }
                else
                {
                    TimeSpan += step;
                }
            }
        }

        private void NotifyCountDownComplete()
        {
            EventHandler handler = OnCountDownComplete;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }


The control defines additional properties for manipulating and customizing the output. The dependecy property IsStarted is used to start/stop the timer. The dependency property IsCountDown is used to specify the behavior whether to increase or decrease the TimeSpan at each tick. Also the control defines the dependency property TimeFormat for displaying the time elapsed in a specific format. The TimeSpan class accepts 'c', 'g' and 'G' as standard format specifiers and formats the output using the common specifier 'c' if none is provided. Also the control defines an event OnCountDownComplete that is raised when the IsCountDown property is set and the TimeSpan value reaches zero.


In order to use the control from XAML, declare the custom XAML namespace and map it to the library that defines the control.

xmlns:kh="clr-namespace:KoderHack.WPF.Controls;assembly=KoderHack.WPF.Controls"

Once the library is available, you can declare the type as follows;

<kh:TimerTextBlock x:Name="ttbCountDown" IsCountDown="True" TimeSpan="00:00:59" IsStarted="True" Width="180" HorizontalAlignment="Center" TextAlignment="Center" FontSize="24" Padding="10" OnCountDownComplete="ttbCountDown_OnCountDownComplete" />


Download Demo and Source