#1,040 – An Example of Dependency Property Inheritance

One of the attributes of dependency properties in WPF is that they support inheritance of property values within a tree of user interface elements.  Controls (dependency objects) within the tree that make use of a particular dependency property can set a property value locally or retrieve a value from an element higher up in the tree.  The local value takes precedence over the inherited value and then in turn applies to all elements “further down”.

(There are a number of other places from which a dependency property can get its value).

Below is an example.  FontSize is set to 20 for the top-level Window.  This value is use for lower-level elements unless they set their own value.  The GroupBox sets FontSize to 14, which then applies to it and elements under it.  Within the GroupBox, the second Label sets its own value for FontSize.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="Dependency Properties" 
        Width="280" Height="252"
        FontSize="20">

    <StackPanel Name="spOuter" 
                Margin="5">
        <Label Name="lbl1" Content="Label 1"/>
        <Label Name="lbl2" Content="Label 2"/>
        <GroupBox Name="gb1" Header="Stuff" FontSize="14">
            <StackPanel Name="spInner" Orientation="Horizontal">
                <Label Name="lbl3" Content="Label 3"/>
                <Label Name="lbl4" Content="Label 4" FontSize="10"/>
            </StackPanel>
        </GroupBox>
        <Button Name="btn1" Content="I'm a Button"
                            Padding="10,5" HorizontalAlignment="Center"
                            Click="btn1_Click"/>
    </StackPanel>
</Window>

1040-001
See this post for another example of dependency property inheritance.

#1,039 – Intercepting Bad Date Strings Entered into a DatePicker

When a user manually enters a date into a DatePicker, the DatePicker control automatically checks to see whether what they entered is a valid date.  If the date is valid, it’s converted to the proper display format and the DatePicker’s SelectedDate property is set.

If the date is not valid, the DatePicker by automatically reverts to the last valid string contained in this field, or to an empty string.

You can react to the user entering an invalid date by handling the DateValidationError event.

For example, if we have a bindable string property ErrorMessage, we can do the following:

        <Label Content="Pick a date:" Margin="5"/>
        <DatePicker Margin="5,0,5,5"
                    DateValidationError="DatePicker_DateValidationError"
                    SelectedDateChanged="DatePicker_SelectedDateChanged"/>
        <TextBlock Margin="5" Text="{Binding ErrorMessage}"
                   Foreground="Red"
                   TextWrapping="Wrap"/>

Code-behind:

        private void DatePicker_DateValidationError(object sender, DatePickerDateValidationErrorEventArgs e)
        {
            ErrorMessage = e.Exception.Message;
        }

        private void DatePicker_SelectedDateChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            ErrorMessage = "";
        }

1039-001
1039-002

#1,038 – Another Way to Prevent Certain Dates from Being Selected

You can limit the dates available for selection in a Calendar or DatePicker control in a few different ways.

If you want to prohibit some combination of dates, but have to do some calculation to figure out which dates should be prohibited, you can add a handler for the SelectedDateChanged event.

        <Label Content="Pick a day to go skydiving:"
               Margin="5"/>
        <DatePicker Margin="5,0,5,5"
                    SelectedDateChanged="DatePicker_SelectedDateChanged" />

In the handler, if the selected date is not one that you allow, you can undo the fact that it was selected.

        private void DatePicker_SelectedDateChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                DateTime dayToDive = (DateTime)e.AddedItems[0];
                if ((dayToDive.DayOfWeek == DayOfWeek.Friday) &&
                    (dayToDive.Day == 13))
                {
                    MessageBox.Show("Dude, that's really an unlucky day to go skydiving.");
                    ((DatePicker)sender).SelectedDate = null;
                }
            }
        }

If we pick a Friday the 13th, the application warns us and then unselects the date.
1038-001

 

1038-002

#1,037 – Entering Text Manually into a DatePicker

When using a DatePicker control, you can either use the dropdown calendar to select a date or you can manually enter a date in the text area.

1037-001

 

When the DatePicker converts the string that you enter into a date, it uses the DateTime.Parse method, which in turn uses the valid date/time formats for your current culture to convert from a string to a date.

For example, if you set the SelectedDateFormat to Long (so that the long form of the date is displayed) and then enter “1/10/13” when in the U.S., the date is interpreted as January 10, 2013.  This is because the normal short date format is month/day/year.  (Note that SelectedDateFormat only dictates how the date is displayed–you can still enter it using the short format).

1037-002

 

However, if you enter “1/10/13” and your regional settings are set to French/France, the date will be interpreted as October 1, 2013.  This is because the short date format in France is day/month/year.

1037-003

#1,036 – Date Formats in the DatePicker Control

You can set the format of the date displayed in the text area of a DatePicker by setting the SelectedDateFormat property to either Short or Long.  These formats correspond to the “short date” and “long date” formats associated with the current culture.  You can see these format strings in the Region dialog.  For example, for English/U.S. they are:

  • Short – “M/d/yyyy” 
  • Long – “dddd, MMMM d, yyyy” 

Where

  • M = Month, 1-12
  • d = Day of month, 1-31
  • yyyy = 4-digit year
  • dddd = Day of week, full name
  • MMMM = Month, full name

1036-001

So, for English/U.S., if we select March 18, 2014 as the date, we see the date displayed as one of the following strings:

  • Short – “3/18/2014”
  • Long – “Tuesday, March 18, 2014”

1036-002

 

 

 

#1,035 – DatePicker Basics

The DatePicker is a control that operates like the Calendar control, allowing the user to select a date, but does so in a dropdown calendar that appears when the user clicks on a calendar icon in the control.  Unlike the Calendar control, the DatePicker allows selecting only a single date.

    <StackPanel>
        <Label Content="Calendar:"/>
        <Calendar />

        <Label Content="DatePicker:" Margin="0,10,0,0"/>
        <DatePicker Margin="5,0"/>
    </StackPanel>

The DatePicker looks a bit like a TextBox, with an area to enter some text and a calendar icon to the right.

1035-001

If you click on the calendar icon in the DatePicker, a calendar appears as a popup, allowing selection of a date.

1035-002

Once you select a date, the date is then displayed in the text area.

1035-003

You can also type a date manually into the text area of the DatePicker.

1035-004

#1,034 – Limiting the Range of Selectable Dates in a Calendar

The Calendar control allows selecting one or more dates.  By default, the user is allowed to select dates within the range DateTime.MinValue (1/1/0001) to DateTime.MaxValue (12/31/9999).

If you want to restrict the user to dates within a more reasonable range, you can set the DisplayDateStart and DisplayDateEnd properties.  The Calendar will then be limited to dates within that range.

        <Label Content="Pick your favorite day from the 1980s:"/>
        <Calendar DisplayDateStart="1/1/1980"
                  DisplayDateEnd="12/31/1989"/>

1034-001
1034-002

These properties also work in the DatePicker control, to limit the dates that can be selected.

 

#1,033 – ProgressBar Can Be Horizontal or Vertical

The ProgressBar is rendered horizontally by default, but you can orient it vertically by setting the Orientation property to Vertical.

    <StackPanel>
        <ProgressBar Value="{Binding TheProgress}"
                     Height="15" Margin="15,15,15,0"/>
        <Label Content="Doing some work..."
               Margin="10,0"/>
        <ProgressBar Value="{Binding TheProgress}"
                     Width="15" Height="100" Margin="15"
                     HorizontalAlignment="Left"
                     Orientation="Vertical"/>
        <Button Margin="15" Padding="15,3" HorizontalAlignment="Center"
            Content="Start" Click="Button_Click"/>
    </StackPanel>

1033-001

#1,032 – Show Progress on Windows Taskbar Icon

When your application is doing some work in the background, it’s common to show progress in a ProgressBar within your application.

1032-001

In Windows 7 and Windows 8, you can also make use of the underlying Windows API to show an integrated progress bar on the application’s taskbar icon.  The progress is rendered using a green bar that moves across the width of the icon.  This is helpful for showing progress while your application is minimized.

1032-002

Below is a complete example of how you can update the taskbar icon to show progress.  Some of the low-level API code was taken from the Windows API Code Pack on MSDN, which contains examples of many more API functions that you can call.  You can use the Windows API Code Pack as an external library, or you can call the low-level Windows API yourself.  The code below does the latter and is the minimum code required to show progress on the taskbar.

To start with, we define the following code in a TaskbarAPI.cs file.  This provides a managed wrapper around the underlying Windows API calls.  The main thing is the ITaskbarList4 interface, in its entirety.  We then define some extra stuff for the types that appear in the interface.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1
{
    internal enum HResult
    {
        Ok = 0x0000

        // Add more constants here, if necessary
    }

    public enum TaskbarProgressBarStatus
    {
        NoProgress = 0,
        Indeterminate = 0x1,
        Normal = 0x2,
        Error = 0x4,
        Paused = 0x8
    }

    internal enum ThumbButtonMask
    {
        Bitmap = 0x1,
        Icon = 0x2,
        Tooltip = 0x4,
        THB_FLAGS = 0x8
    }

    [Flags]
    internal enum ThumbButtonOptions
    {
        Enabled = 0x00000000,
        Disabled = 0x00000001,
        DismissOnClick = 0x00000002,
        NoBackground = 0x00000004,
        Hidden = 0x00000008,
        NonInteractive = 0x00000010
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct ThumbButton
    {
        ///
/// WPARAM value for a THUMBBUTTON being clicked.
        ///
        internal const int Clicked = 0x1800;

        [MarshalAs(UnmanagedType.U4)]
        internal ThumbButtonMask Mask;
        internal uint Id;
        internal uint Bitmap;
        internal IntPtr Icon;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        internal string Tip;
        [MarshalAs(UnmanagedType.U4)]
        internal ThumbButtonOptions Flags;
    }

    internal enum SetTabPropertiesOption
    {
        None = 0x0,
        UseAppThumbnailAlways = 0x1,
        UseAppThumbnailWhenActive = 0x2,
        UseAppPeekAlways = 0x4,
        UseAppPeekWhenActive = 0x8
    }

    // using System.Runtime.InteropServices
    [ComImportAttribute()]
    [GuidAttribute("c43dc798-95d1-4bea-9030-bb99e2983a1a")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface ITaskbarList4
    {
        // ITaskbarList
        [PreserveSig]
        void HrInit();
        [PreserveSig]
        void AddTab(IntPtr hwnd);
        [PreserveSig]
        void DeleteTab(IntPtr hwnd);
        [PreserveSig]
        void ActivateTab(IntPtr hwnd);
        [PreserveSig]
        void SetActiveAlt(IntPtr hwnd);

        // ITaskbarList2
        [PreserveSig]
        void MarkFullscreenWindow(
            IntPtr hwnd,
            [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);

        // ITaskbarList3
        [PreserveSig]
        void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal);
        [PreserveSig]
        void SetProgressState(IntPtr hwnd, TaskbarProgressBarStatus tbpFlags);
        [PreserveSig]
        void RegisterTab(IntPtr hwndTab, IntPtr hwndMDI);
        [PreserveSig]
        void UnregisterTab(IntPtr hwndTab);
        [PreserveSig]
        void SetTabOrder(IntPtr hwndTab, IntPtr hwndInsertBefore);
        [PreserveSig]
        void SetTabActive(IntPtr hwndTab, IntPtr hwndInsertBefore, uint dwReserved);
        [PreserveSig]
        HResult ThumbBarAddButtons(
            IntPtr hwnd,
            uint cButtons,
            [MarshalAs(UnmanagedType.LPArray)] ThumbButton[] pButtons);
        [PreserveSig]
        HResult ThumbBarUpdateButtons(
            IntPtr hwnd,
            uint cButtons,
            [MarshalAs(UnmanagedType.LPArray)] ThumbButton[] pButtons);
        [PreserveSig]
        void ThumbBarSetImageList(IntPtr hwnd, IntPtr himl);
        [PreserveSig]
        void SetOverlayIcon(
          IntPtr hwnd,
          IntPtr hIcon,
          [MarshalAs(UnmanagedType.LPWStr)] string pszDescription);
        [PreserveSig]
        void SetThumbnailTooltip(
            IntPtr hwnd,
            [MarshalAs(UnmanagedType.LPWStr)] string pszTip);
        [PreserveSig]
        void SetThumbnailClip(
            IntPtr hwnd,
            IntPtr prcClip);

        // ITaskbarList4
        void SetTabProperties(IntPtr hwndTab, SetTabPropertiesOption stpFlags);
    }

    [GuidAttribute("56FDF344-FD6D-11d0-958A-006097C9A090")]
    [ClassInterfaceAttribute(ClassInterfaceType.None)]
    [ComImportAttribute()]
    internal class CTaskbarList { }
}

Next, we define a little helper class that calls the API on our behalf.  We use a Dispatcher so that we can get access to the application’s main window, doing this on the UI thread.

using System;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace WpfApplication1
{
    public static class TaskbarUtility
    {
        private static ITaskbarList4 _taskbarList;

        static TaskbarUtility()
        {
            if (!IsSupported())
                throw new Exception("Taskbar functions not available");

            _taskbarList = (ITaskbarList4)new CTaskbarList();
            _taskbarList.HrInit();
        }

        private static bool IsSupported()
        {
            return Environment.OSVersion.Platform == PlatformID.Win32NT &&
                Environment.OSVersion.Version.CompareTo(new Version(6, 1)) >= 0;
        }

        public static void SetProgressState(TaskbarProgressBarStatus state)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
            {
                _taskbarList.SetProgressState((new WindowInteropHelper(Application.Current.MainWindow)).Handle, state);
            }));
        }

        public static void SetProgressValue(int currentValue, int maximumValue)
        {
            // using System.Windows.Interop
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
            {
                _taskbarList.SetProgressValue(
                    (new WindowInteropHelper(Application.Current.MainWindow)).Handle,
                    Convert.ToUInt64(currentValue),
                    Convert.ToUInt64(maximumValue));
            }));
        }
    }
}

The XAML for the example just contains a ProgressBar and a Button to start the background operation.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="ProgressBar"
        Width="280" Height="140">

    <StackPanel>
        <ProgressBar Value="{Binding TheProgress}"
                     Height="15" Margin="15,15,15,0"/>
        <Label Content="Doing some work..."
               Margin="10,0"/>
        <Button Margin="15" Padding="15,3" HorizontalAlignment="Center"
            Content="Start" Click="Button_Click"/>
    </StackPanel>
</Window>

Our code-behind is also fairly simple.  When the user clicks the Button, we start an operation on the background thread and periodically update both the ProgressBar control and the progress bar built into the taskbar icon.

using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private int theProgress = 0;
        public int TheProgress
        {
            get { return theProgress; }
            protected set
            {
                theProgress = value;
                RaisePropertyChanged("TheProgress");
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            new Thread(() =>
            {
                TaskbarUtility.SetProgressState(TaskbarProgressBarStatus.Normal);

                for (int i = 1; i <= 100; i++)
                {
                    TheProgress = i;
                    TaskbarUtility.SetProgressValue(i, 100);
                    Thread.Sleep(50);
                }

                TheProgress = 0;
                TaskbarUtility.SetProgressState(TaskbarProgressBarStatus.NoProgress);
            }).Start();
        }

        // INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        private void RaisePropertyChanged(string propName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

#1,031 – Update a ProgressBar from a Background Thread

If you try to update a ProgressBar in a chunk of code that is running on the main UI thread and doing some work, the ProgressBar won’t update until all of the work is done.  In the example below, the ProgressBar won’t actually update until the loop finishes executing.

for (int i = 0; i < 100; i++)
 {
     pbTest.Value = i;
     Thread.Sleep(50);
 }

To allow the ProgressBar to update immediately, you need to do two things:

  • Do the work on a background thread
  • Periodically update the ProgressBar using a bound property (rather than directly)

We do the work on the background thread to avoid blocking the UI thread.  But we’re not allowed to update the ProgressBar from this thread because we aren’t the thread that created the control.  Instead, we update progress by setting a property that we then bind the ProgressBar.Value to.

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            new Thread(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    TheProgress = i;
                    Thread.Sleep(50);
                }
            }).Start();
        }