Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CategorySlider - A discrete slider with objects (categories) insted of values

0.00/5 (No votes)
17 May 2013 1  
Using a Slider to select an item from a list of objects (string, numeric, or image) as a discrete value or category.

Introduction 

The initial problem was that I wanted to use a slider to make a value selection from a non-linear scale and wanted it to round that value to the nearest integer. Values would need to be something like: 1, 5, 10, 50, 100, 500. I was able to round the value of a normal slider but it would only give me values in a linear scale. Googling around I couldn't find any solution for a slider with a discrete set of values so I started coding.

I came up with a UserControl as a much better solution than I needed in the first place and decided to share it. It can mix numeric values, strings and images as "labels" for each category on the slider, working both horizontally and vertically.

In the example above I have strings, ints, doubles, and images as slider labels. Any improvements and suggestions would be welcome, of course. I made it with VS 2010 for Silverlight 4 and .NET Framework 4.

Basics

The basic idea behind this slider is to use a normal slider, round its value and use it as an index to a collection of items, returning the referenced item as the value of the CategorySlider. Some needs arose while coding. I needed to pass to the slider in the UserControl some basic setup values such as Orientation and IsDirectionReversed. Also needed to know, as returning values, not only the selected object, but it's index as well. I needed a ValueChanged event and a few more things I will explain further.

The UserControl 

This code is all there is to it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace Tools
{
    public partial class CategorySlider : UserControl
    {
        public event ValueChangedEventArgs.ValueChangedHandler ValueChanged;

        #region Public DependencyProperties
        private static DependencyProperty OrientationProperty = 
          DependencyProperty.Register("Orientation", typeof(Orientation), 
          typeof(CategorySlider), new PropertyMetadata(Orientation.Horizontal));
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        private static DependencyProperty IsDirectionReversedProperty = 
          DependencyProperty.Register("IsDirectionReversed", typeof(bool), 
          typeof(CategorySlider), new PropertyMetadata(false));
        public bool IsDirectionReversed
        {
            get { return (bool)GetValue(IsDirectionReversedProperty); }
            set { SetValue(IsDirectionReversedProperty, value); }
        }

        private static DependencyProperty ShowLabelsProperty = 
          DependencyProperty.Register("ShowLabels", typeof(bool), 
          typeof(CategorySlider), new PropertyMetadata(true));
        public bool ShowLabels
        {
            get { return (bool)GetValue(ShowLabelsProperty); }
            set { SetValue(ShowLabelsProperty, value); }
        }

        private static DependencyProperty ListObjectsProperty = 
          DependencyProperty.Register("ListObjects", typeof(List<object>), 
          typeof(CategorySlider), new PropertyMetadata(new List<object>(), 
          new PropertyChangedCallback(ListObjectsChange)));
        public List<object> ListObjects
        {
            get { return (List<object>)GetValue(ListObjectsProperty); }
            set { SetValue(ListObjectsProperty, value); }
        }

        private static DependencyProperty CategoryValueProperty = 
          DependencyProperty.Register("CategoryValue", typeof(object), 
          typeof(CategorySlider), new PropertyMetadata(new object()));
        public object CategoryValue
        {
            get { return (object)GetValue(CategoryValueProperty); }
            set { SetValue(CategoryValueProperty, value); }
        }

        private static DependencyProperty CategoryIndexProperty = 
          DependencyProperty.Register("CategoryIndex", typeof(int), 
          typeof(CategorySlider), new PropertyMetadata((int)0));
        public int CategoryIndex
        {
            get { return (int)GetValue(CategoryIndexProperty); }
            set { SetValue(CategoryIndexProperty, value); }
        }
        #endregion Public DependencyProperties

        #region Private DependencyProperties
        private static DependencyProperty SliderMaxValueProperty = 
          DependencyProperty.Register("SliderMaxValue", typeof(int), 
          typeof(CategorySlider), new PropertyMetadata((int)0));
        private int SliderMaxValue
        {
            get { return (int)GetValue(SliderMaxValueProperty); }
            set { SetValue(SliderMaxValueProperty, value); }
        }
        #endregion Private DependencyProperties

        public CategorySlider()
        {
            InitializeComponent();
        }

        private void Slider_ValueChanged(object sender, 
          System.Windows.RoutedPropertyChangedEventArgs<double> e)
        {
            //Rounding the slider value to the nearest integer
            ((Slider)sender).Value = Math.Round(e.NewValue);
            if (this.ListObjects.Count > 0)
                this.CategoryValue = this.ListObjects.ElementAt((int)((Slider)sender).Value);
            if (this.ValueChanged != null && Math.Round(e.OldValue) != Math.Round(e.NewValue))
                this.ValueChanged(this, new ValueChangedEventArgs(this.CategoryIndex, this.CategoryValue));
        }

        #region DependencyProperties Callback Methods
        private static void ListObjectsChange(DependencyObject d, 
          DependencyPropertyChangedEventArgs e) { (d as CategorySlider).ListObjectsChange(e); }
        private void ListObjectsChange(DependencyPropertyChangedEventArgs e)
        {
            //Check if the list has items
            if (((List<object>)e.NewValue).Count > 0)
            {
                this.SliderMaxValue = ((List<object>)e.NewValue).Count - 1;

                //Adjusting spacing between labels
                GridLength size = new GridLength(0.5 / (SliderMaxValue - 1), GridUnitType.Star);
                this.HorizontalLabelsTemplate.ColumnDefinitions.First().Width = size;
                this.HorizontalLabelsTemplate.ColumnDefinitions.Last().Width = size;
                this.VerticalLabelsTemplate.RowDefinitions.First().Height = size;
                this.VerticalLabelsTemplate.RowDefinitions.Last().Height = size;

                //Insert first label (left alignment if horizontal, bottom if vertical)
                this.InsertLabel(this.Orientation == Orientation.Horizontal ? 
                              this.HorizontalLabelsTemplate : this.VerticalLabelsTemplate,
                              this.IsDirectionReversed ? ((List<object>)e.NewValue).Last() : 
                              ((List<object>)e.NewValue).First(),
                              this.Orientation == Orientation.Horizontal ? 0 : 1, 
                              HorizontalAlignment.Left, VerticalAlignment.Bottom,
                              2);

                //Check if the list has more than one item
                if (((List<object>)e.NewValue).Count > 1)
                {
                    //Insert last label (right alignment if horizontal, top if vertical)
                    this.InsertLabel(this.Orientation == Orientation.Horizontal ? 
                                  this.HorizontalLabelsTemplate : this.VerticalLabelsTemplate,
                                  this.IsDirectionReversed ? ((List<object>)e.NewValue).First() : 
                                  ((List<object>)e.NewValue).Last(),
                                  this.Orientation == Orientation.Horizontal ? 1 : 0, 
                                  HorizontalAlignment.Right, VerticalAlignment.Top,
                                  2);

                    //Check if the list has more than two items
                    if (((List<object>)e.NewValue).Count > 2)
                    {
                        //Create a list with the middle labels
                        List<object> tmpList = ((List<object>)e.NewValue).Where(
                          c => c != ((List<object>)e.NewValue).First() && 
                          c != ((List<object>)e.NewValue).Last()).ToList();
                        //Reverse it if needed
                        if (this.Orientation == Orientation.Vertical && !this.IsDirectionReversed ||
                                 this.Orientation == Orientation.Horizontal && this.IsDirectionReversed)
                            tmpList.Reverse();

                        foreach (object item in tmpList)
                        {
                            //For each label insert a new column or a new row in the middleGrid
                            if (this.Orientation == Orientation.Horizontal)
                            {
                                ColumnDefinition coluna = new ColumnDefinition();
                                coluna.Width = new GridLength(1, GridUnitType.Star);
                                MiddleHItemsLabels.ColumnDefinitions.Add(coluna);
                            }
                            else
                            {
                                RowDefinition linha = new RowDefinition();
                                linha.Height = new GridLength(1, GridUnitType.Star);
                                MiddleVItemsLabels.RowDefinitions.Add(linha);
                            }

                            //Insert middle label
                            this.InsertLabel(this.Orientation == Orientation.Horizontal ? 
                                          this.MiddleHItemsLabels : this.MiddleVItemsLabels,
                                          item,
                                          tmpList.IndexOf(item), 
                                          HorizontalAlignment.Center, VerticalAlignment.Center,
                                          1);
                        }
                    }
                }
                CategoryValue = ((List<object>)e.NewValue).ElementAt((int)(slider1.Value));
                slider1.IsHitTestVisible = true;
            }
            else
                slider1.IsHitTestVisible = false;
        }

        private void InsertLabel(Grid parent, object item, int pos, 
          HorizontalAlignment alignIfHor, VerticalAlignment alignIfVer, int span)
        {
            Image newImage1 = new Image();
            TextBlock newLabel = new TextBlock();

            //If label is not an image it's surely "stringble"
            if (item.GetType() == typeof(Image))
                newImage1 = (Image)item;
            else
                newLabel.Text = item.ToString();

            //Setup of the label
            if (this.Orientation == Orientation.Horizontal)
            {
                if (item.GetType() == typeof(Image))
                {
                    newImage1.HorizontalAlignment = alignIfHor;
                    newImage1.VerticalAlignment = VerticalAlignment.Bottom;
                    newImage1.Width = Double.NaN;
                    newImage1.SetValue(Grid.ColumnProperty, pos);
                    newImage1.SetValue(Grid.ColumnSpanProperty, span);
                }
                else
                {
                    newLabel.HorizontalAlignment = alignIfHor;
                    newLabel.VerticalAlignment = VerticalAlignment.Bottom;
                    newLabel.Width = Double.NaN;
                    newLabel.SetValue(Grid.ColumnProperty, pos);
                    newLabel.SetValue(Grid.ColumnSpanProperty, span);
                }
            }
            else
            {
                if (item.GetType() == typeof(Image))
                {
                    newImage1.HorizontalAlignment = HorizontalAlignment.Right;
                    newImage1.VerticalAlignment = alignIfVer;
                    newImage1.Height = Double.NaN;
                    newImage1.SetValue(Grid.RowProperty, pos);
                    newImage1.SetValue(Grid.RowSpanProperty, span);
                }
                else
                {
                    newLabel.HorizontalAlignment = HorizontalAlignment.Right;
                    newLabel.VerticalAlignment = alignIfVer;
                    newLabel.Height = Double.NaN;
                    newLabel.SetValue(Grid.RowProperty, pos);
                    newLabel.SetValue(Grid.RowSpanProperty, span);
                }
            }

            //Isert the label on the grid
            if (item.GetType() == typeof(Image))
                parent.Children.Add(newImage1);
            else
                parent.Children.Add(newLabel);
        }
        #endregion DependencyProperties Callback Methods
    }

    #region Converters
    public class ReverseVisibility : IValueConverter
    {
        // Visibility = !Visibility
        public object Convert(object value, Type targetType, 
               object parameter, System.Globalization.CultureInfo culture)
        {
            return (Visibility)value == Visibility.Collapsed ? 
                     Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, 
               object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class OrientationToVisibilityConverter : IValueConverter
    {
        // parameter = H => Visible if Horizontal
        // parameter = V => Visible if Vertical
        public object Convert(object value, Type targetType, object parameter, 
                              System.Globalization.CultureInfo culture)
        {
            if (parameter == null || value == null) return Visibility.Collapsed;
            return ((CategorySlider)value).ShowLabels && (((
               (CategorySlider)value).Orientation == Orientation.Horizontal && 
               (string)parameter == "H") || (((CategorySlider)value).Orientation == 
                Orientation.Vertical && (string)parameter == "V")) ? 
                Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, 
          object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    #endregion Converters

    public class ValueChangedEventArgs : EventArgs
    {
        public delegate void ValueChangedHandler(object sender, ValueChangedEventArgs e);

        public int CategoryIndex { get; set; }
        public object CategoryValue { get; set; }
        public ValueChangedEventArgs(int categoryIndex, object categoryValue)
        {
            CategoryIndex = categoryIndex;
            CategoryValue = categoryValue;
        }
    }
}
<UserControl x:Class="Portal_Tools.CategorySlider"
    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"
    xmlns:local="clr-namespace:Portal_Tools" 
    x:Name="userControl"
    mc:Ignorable="d"
    d:DesignHeight="30" d:DesignWidth="400">

    <UserControl.Resources>
        <local:OrientationToVisibilityConverter x:Key="OrientationToVisibilityConverter"/>
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid x:Name="HorizontalLabelsTemplate" Grid.Column="1" 
              VerticalAlignment="Bottom" 
              Visibility="{Binding ConverterParameter=H, Converter={StaticResource 
                 OrientationToVisibilityConverter}, ElementName=userControl, Mode=OneWay}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid x:Name="MiddleHItemsLabels" 
                     Grid.Column="1" Margin="5,0"/>
        </Grid>
        <Grid x:Name="VerticalLabelsTemplate" Grid.Row="1" 
                HorizontalAlignment="Right" Visibility="{Binding ConverterParameter=V, 
                  Converter={StaticResource OrientationToVisibilityConverter}, 
                  ElementName=userControl, Mode=OneWay}" >
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid x:Name="MiddleVItemsLabels" Grid.Row="1" Margin="0,5"/>
        </Grid>
        <Slider x:Name="slider1" 
          Maximum="{Binding SliderMaxValue, ElementName=userControl}" 
          ValueChanged="Slider_ValueChanged" SmallChange="1" 
          Grid.Row="1" Grid.Column="1" 
          Orientation="{Binding Orientation, ElementName=userControl}" 
          Cursor="{Binding Cursor, ElementName=userControl}" 
          IsDirectionReversed="{Binding IsDirectionReversed, ElementName=userControl}" 
          Value="{Binding CategoryIndex, ElementName=userControl, Mode=TwoWay}"/>
    </Grid>
</UserControl>

Points of Interest 

Some explanations on how it works.  

Public DependencyProperties

  • Orientation and IsDirectionReversed - Used to pass those settings to the Slider, to show/hide horizontal and vertical grids and determine the order of adding the labels to the label grids.
  • ShowLabels - Although we can have categories or discrete values, there is an option not to show the labels.
  • ListObjects - This is where we deliver the list of objects to be used as labels to the categories. They can be any type of objects but the ones that made sense to me were string, numeric values (int, double, ...) and images.
  • CategoryValue - This DP has the object selected by the CategorySlider. It is obtained from the list above using the slider value as the index. This object is changed only when the rounded slider value changes.
  • CategoryIndex - The rounded slider value.

DependencyProperties CallBacks 

  • ListObjectsChange() - This is where all the magic happens. When the ListObjects changes it sets the grids column widths / row heights depending on the ListObjects count, sets up first and last labels (see XAML Structure below), sets up the MiddleLabels grids and appends the labels.

Private DependencyProperties

  • SliderMaxValue - Used only internally. Where the slider Maximumis bounded to. Depends on the ListObjects count.

Events 

  • CategorySlider.ValueChanged - If triggered, fires back the event when the rounded value of the slider also changes (catched by the Slider.ValueChanged event).
  • Class ValueChangedEventArgs - Used by the CategorySlider.ValueChanged event to return to the caller the selected object (ValueChangedEventArgs.CategoryValue) and selected index (ValueChangedEventArgs.CategoryIndex).

Hooked Events 

  • Slider.ValueChanged - This is where the changes on the slider value are treated (rounded) and tested for effective changes after rounding. If value effectively changes the CategoryValue and CategoryIndex are updated.

Converters 

  • ReverseVisibility - This is a generic converter I use across projects that binds to the Visibility property of a control and reverses it. It's great when we need mutually exclusive visibility between two or more controls. This converter is no longer used but I left it in the code. It's very useful and can be used anywhere.

OrientationToVisibilityConverter - The name says it all. Basically it turns os or off the visibility of the HorizontalLabelsTemplate grid depending on the Orientation value.

XAML structure 

As label count in unknown at the beginning and first/last label formatting are different from the middle labels (first label is left/top aligned, ...), I created a two level grid. The first grid has three columns/rows (horizontal or vertical CategorySlider). One for the first label, one for the last label and one for the middle labels grid. Columns/rows and labels on the middle grids are added in the code behind in the foreach cycle on the ListObjectsChange() method. All this fits into a upper level Grid that contains also a normal slider.

Testing

The download at the top includes a MainPage for testing. It's just a Grid with a vertical and an horizontal CategorySlider. I added some controls to demonstrate how to get the CategorySlider value and index either by binding or by getting the property's values making use of the ValueChanged event.

<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"
    xmlns:Tools="clr-namespace:Tools"
    x:Class="TesteCategorySlider.MainPage"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Margin="0,0,20,0">
    	<Grid.ColumnDefinitions>
    		<ColumnDefinition Width="Auto"/>
    		<ColumnDefinition/>
    	</Grid.ColumnDefinitions>
        <Grid Width="200">
        	<Grid.RowDefinitions>
        		<RowDefinition Height="Auto"/>
        		<RowDefinition Height="Auto"/>
        		<RowDefinition/>
        	</Grid.RowDefinitions>
        	<TextBlock x:Name="EventV" HorizontalAlignment="Left" 
        	    TextWrapping="Wrap" Text="TextBlock" d:LayoutOverrides="Height"/>
        	<Grid Grid.Row="1">
        		<Grid.ColumnDefinitions>
        			<ColumnDefinition Width="Auto"/>
        			<ColumnDefinition/>
        		</Grid.ColumnDefinitions>
        		<TextBlock x:Name="indexV" TextWrapping="Wrap" 
        		  Text="{Binding CategoryIndex, ElementName=categorySliderV}" 
        		  d:LayoutOverrides="Width" VerticalAlignment="Top" 
        		  Padding="3,0" Foreground="#FF008114"/>
        		<TextBlock x:Name="objectV" 
        		  Text="{Binding CategoryValue, ElementName=categorySliderV}" 
        		  VerticalAlignment="Top" Grid.Column="1" 
        		  Foreground="#FF003EFF" Padding="3,0,0,0" />
        	</Grid>
        	<Tools:CategorySlider x:Name="categorySliderV" Foreground="#FF0010FF" 
        	  FontWeight="Bold" FontSize="13.333" Orientation="Vertical" 
        	  HorizontalAlignment="Center" Grid.Row="2" 
        	  VerticalContentAlignment="Stretch" ShowLabels="False"/>
        </Grid>
        <Grid Grid.Column="1">
        	<Grid.RowDefinitions>
        		<RowDefinition/>
        		<RowDefinition Height="Auto"/>
        		<RowDefinition/>
        	</Grid.RowDefinitions>
        	<TextBlock x:Name="EventH" TextWrapping="Wrap" 
        	  Text="TextBlock" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
        	<Grid VerticalAlignment="Bottom" HorizontalAlignment="Center" 
        	  Grid.Row="1" Margin="0,0,0,20">
        		<Grid.ColumnDefinitions>
        			<ColumnDefinition Width="Auto"/>
        			<ColumnDefinition/>
        		</Grid.ColumnDefinitions>
        		<TextBlock x:Name="indexH" TextWrapping="Wrap" 
        		  Text="{Binding CategoryIndex, ElementName=categorySliderH}" 
        		  d:LayoutOverrides="Height" Padding="3,0" 
        		  Foreground="#FF008114"/>
        		<TextBlock x:Name="objectH" 
        		  Text="{Binding CategoryValue, ElementName=categorySliderH}" 
        		  VerticalAlignment="Bottom" Grid.Column="1" 
        		  Padding="3,0,0,0" Foreground="#FF003EFF" />
        	</Grid>
        	<Tools:CategorySlider x:Name="categorySliderH" 
               Foreground="#FF0010FF" FontWeight="Bold" 
               FontSize="13.333" VerticalAlignment="Top" Grid.Row="2"/>
        </Grid>
    </Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;

namespace TesteCategorySlider
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            //CategorySlyder lists
            categorySliderV.ListObjects = new List<object>() { "Op. One", 2, 3.35, 
              "Blue", "Moon", new Image() { Source = new BitmapImage(
              new Uri("/TesteCategorySlider;component/ZoomIn.png", UriKind.Relative)), 
              Width = 30, Height = 30 }, new Image() { Source = new BitmapImage(new Uri(
              "/TesteCategorySlider;component/ZoomOut.png", UriKind.Relative)), 
              Width = 30, Height = 30 }, 9.1, 100, "Last" };
            categorySliderH.ListObjects = new List<object>() { "Op. One", 2, 3.35, 
              "Blue", "Moon", new Image() { Source = new BitmapImage(new Uri(
              "/TesteCategorySlider;component/ZoomIn.png", UriKind.Relative)), Width = 30, 
              Height = 30 }, new Image() { Source = new BitmapImage(new Uri(
              "/TesteCategorySlider;component/ZoomOut.png", UriKind.Relative)), 
              Width = 30, Height = 30 }, 9.1, 100, "Last" };

            //CategorySlider event hooking
            categorySliderV.ValueChanged += 
              new Tools.ValueChangedEventArgs.ValueChangedHandler(categorySliderV_ValueChanged);
            categorySliderH.ValueChanged += 
              new Tools.ValueChangedEventArgs.ValueChangedHandler(categorySliderH_ValueChanged);

            //Geting initial value
            EventV.Text = categorySliderV.CategoryIndex.ToString();
            EventH.Text = categorySliderH.CategoryIndex.ToString();
        }

        void categorySliderH_ValueChanged(object sender, Tools.ValueChangedEventArgs e)
        {
            EventH.Text = e.CategoryIndex.ToString();
        }

        void categorySliderV_ValueChanged(object sender, Tools.ValueChangedEventArgs e)
        {
            EventV.Text = e.CategoryIndex.ToString();
        }
    }
}

Conclusion

It works for the purpose I made it for. Surely there might be some issues that I haven't tested yet (I'm thinking of styling as an example ...). Enjoy it and make it better! I'd like to know how You liked this article.

Please don't forget to vote!

History

Some suggestions really make sense. 

Here they are:  

2013 May 17 - Possibility to not show the labels

  • Additional DependencyProperty - ShowLabels.
  • Dropped converter ReverseVisibility (left it in the code for it might be useful).
  • Changed converter OrientationToVisibilityConverter.
  • Changed XAML accordingly.
  • Zip is updated.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here