Click here to Skip to main content
15,891,431 members
Articles / Desktop Programming / WPF

The WPF Podcatcher Series - Part 2 (Structural Skinning)

Rate me:
Please Sign up or sign in to vote.
4.97/5 (58 votes)
5 Mar 2008CPOL16 min read 253.2K   7.2K   166  
The second article in a series devoted to a WPF application that plays streaming audio podcasts off the Internet. This article discusses the idea and implementation of look-less applications.
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using Podder.Model;

namespace Podder.GrantHinkson.Views
{
    /// <summary>
    /// Interaction logic for PodcastsControlView.xaml
    /// </summary>
    public partial class PodcastsControlView : UserControl
    {
        #region Data

        private PodderDataSource _dataSource;
        private readonly Storyboard _closeAddPodcastDialogStoryboard;

        #endregion // Data

        #region Constructor

        public PodcastsControlView()
        {
            InitializeComponent();

            _closeAddPodcastDialogStoryboard = this.TryFindResource("CloseAddPodcastDialog") as Storyboard;

            // Grab a reference to the data source when it becomes available.
            this.DataContextChanged += delegate { _dataSource = base.DataContext as PodderDataSource; };

            // Scroll the selected Podcast into view.
            this.Loaded += delegate { this.PodcastsCarousel.ScrollInfo.SetHorizontalOffset(this.PodcastsCarousel.SelectedIndex); };

            this.PodcastsCarousel.PreviewKeyDown += PodcastsCarousel_PreviewKeyDown;
        }

        #endregion // Constructor

        #region Event Handlers
        #region Add Podcast Area
        void OnAddPodcastAreaClosed(object sender, EventArgs e)
        {
            if (_dataSource != null)
                _dataSource.Podcasts.NewPodcastInfo.FeedUrl = null;

            if (!this.btnAddPodcast.IsEnabled)
                this.btnAddPodcast.IsEnabled = true;
        }

        void OnAddPodcastAreaOpened(object sender, EventArgs e)
        {
            if (!this.IsMouseOverAddPodcastArea)
            {
                // If the user accidentally opened the "Add Podcast" 
                // area then close it for them, as a convenience.
                if (_closeAddPodcastDialogStoryboard != null)
                    _closeAddPodcastDialogStoryboard.Begin(this);
            }
            else
            {
                // We must disable the Add Podcast (+) button because mousing
                // over it while the TextBox is visible causes it to reset.
                // This is because its "mouseover" animations run again.
                if (this.btnAddPodcast.IsEnabled)
                    this.btnAddPodcast.IsEnabled = false;

                if (Commands.AutoDetectFeedUrlOnClipboard.CanExecute(null, this))
                {
                    Commands.AutoDetectFeedUrlOnClipboard.Execute(null, this);
                    CommandManager.InvalidateRequerySuggested();
                }
            }
        }

        /// <summary>
        /// Selects and scrolls the newly added Podcast into view, and erases new podcast URL.
        /// </summary>
        void OnPodcastAdded(object sender, EventArgs e)
        {
            if (_dataSource == null)
                return;

            string feedUrl = _dataSource.Podcasts.NewPodcastInfo.FeedUrl;
            var podcast = _dataSource.Podcasts.FirstOrDefault(p => p.RssUrl == feedUrl);
            if (podcast == null)
                return;

            int idx = this.PodcastsCarousel.Items.IndexOf(podcast);
            if (idx < 0)
                return;

            this.PodcastsCarousel.SelectedIndex = idx;
            this.PodcastsCarousel.ScrollInfo.SetHorizontalOffset(idx);

            _dataSource.Podcasts.NewPodcastInfo.FeedUrl = null;
            this.btnAddPodcast.IsEnabled = true;
        }
        #endregion // Add Podcast Area

        #region Buttons
        void btnCarouselNavPrev_Click(object sender, RoutedEventArgs e)
        {
            this.PodcastsCarousel.ScrollInfo.LineUp();
        }

        void btnCarouselNavNext_Click(object sender, RoutedEventArgs e)
        {
            this.PodcastsCarousel.ScrollInfo.LineDown();
        }
        #endregion Buttons

        #region PodcastsCarousel
        void PodcastsCarousel_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Delete:
                    // Do not allow the user to delete via the Delete key because that bypasses the Delete command.
                    e.Handled = true;
                    break;

                case Key.Right:
                case Key.Down:
                    if (this.PodcastsCarousel.SelectedIndex == this.PodcastsCarousel.Items.Count - 1)
                    {
                        e.Handled = true;
                        this.PodcastsCarousel.SelectedIndex = 0;
                    }
                    break;

                case Key.Left:
                case Key.Up:
                    if (this.PodcastsCarousel.SelectedIndex == 0)
                    {
                        e.Handled = true;
                        this.PodcastsCarousel.SelectedIndex = this.PodcastsCarousel.Items.Count - 1;
                    }
                    break;
            }
        }
        #endregion // PodcastsCarousel
        #endregion // Event Handlers

        #region Private Helpers

        /// <summary>
        /// Returns true if the mouse cursor is over the "Add Podcasts" area of the UI.
        /// </summary>
        bool IsMouseOverAddPodcastArea
        {
            get
            {
                // The IsMouseOver property is not always accurate, so we manually detect if the cursor is over the controls.

                Point grpMousePos = Mouse.GetPosition(this.grpAddPodcast);
                Point btnMousePos = Mouse.GetPosition(this.btnAddPodcast);

                Rect grpAddPodcastBounds = new Rect(0, 0, this.grpAddPodcast.ActualWidth, this.grpAddPodcast.ActualHeight);
                Rect btnAddPodcastBounds = new Rect(0, 0, this.btnAddPodcast.ActualWidth, this.btnAddPodcast.ActualHeight);

                return grpAddPodcastBounds.Contains(grpMousePos) || btnAddPodcastBounds.Contains(btnMousePos);
            }
        }

        #endregion // Private Helpers
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Josh creates software, for iOS and Windows.

He works at Black Pixel as a Senior Developer.

Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.

Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.

Check out his Advanced MVVM[^] book.

Visit his WPF blog[^] or stop by his iOS blog[^].

See his website Josh Smith Digital[^].

Comments and Discussions