Friday, April 12, 2019

Fun with Task Bar buttons

There are quite a few things a WPF application can do with the Task Bar button that cannot be done in a hosted application such as asp.net. This project demonstrates some of those things.

  • Custom Jumplists
  • Progress bar
  • Overlays

Custom Jumplists

Start a new WPF project called TaskBarDemo. I'm using Visual Studio 2019, Framework 4.7, and C#.

The JumpList is a customized context-sensitive menu displayed when the user right-clicks on the task bar button for an application. It's defined in App.xaml.  I'm hard-coding mine, but you can also manipulate it at run time. It occurs to me you could provide links to your application's documentation, possibly customizing it based on your user's security.

In App.xaml, add the jump list like this (substitute your file paths).

<Application x:Class="TaskbarDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TaskbarDemo"
             StartupUri="MainWindow.xaml">
    <JumpList.JumpList>
        <JumpList>
            <JumpTask Title="Transmittal Receiving Guide"
                      Description="Guide to the APY Transmittal Receiving process"
                      ApplicationPath="%PROGRAMFILES%\Microsoft Office\root\Office16\WINWORD.EXE"
                      IconResourcePath="%PROGRAMFILES%\Microsoft Office\root\Office16\WINWORD.EXE"
                      Arguments='"APY Transmittal Receiving Guide.docx"'
                      CustomCategory="Documentation"/>
            <JumpTask Title="Creating a Payment"
                      Description="Simple Steps for Creating a Payment"
                      ApplicationPath="%PROGRAMFILES%\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
                      IconResourcePath="%PROGRAMFILES%\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
                      Arguments='"Simple Steps for Creating a Payment.pdf"'
                      CustomCategory="Documentation"/>
        </JumpList>
    </JumpList.JumpList>
</Application>

JumpTask has a number of useful properties.
  • Title: The text displayed for the jump task
  • Description: The tooltip for the jump task
  • ApplicationPath: The fully qualified path to the executable to launch - this honors environment variables
  • IconResourcePath: The fully qualified path to the executable that contains the icon to display on the jump task
  • IconResourceIndex: Allows the jump task to display alternative icons from the executable
  • Arguments: Arguments passed to executable. Arguments are space delimited, so a single argument that contains spaces must be surrounded by quotes hence '"xxx xxx"'
  • CustomCategory: groups and titles jump tasks
If you are launching a registered file type and want to allow the registered application to launch it, you can set ApplicationPath to the file path, for example.


<JumpTask Title="CNN"
          Description="CNN Website"
          ApplicationPath="http://www.cnn.com"
          CustomCategory="News"/>



There are also JumpPath, Recently used, and Frequently used classes that can be used to launch this application with specific parameters, but are not used to launch other applications.

Progress Bar

The task button can act as a progress bar via the TaskBarItemInfo class. You can alter the color, width, and visibility of the progress bar. Let's shift our attention to the MainWindow. We need to define the TaskBarItemInfo class and bind some of its properties. The ProgressState property controls the visibility and color of the progress bar while the ProgressValue controls the width of the progress bar.

While we're there, we will also define the rest of the window's controls.

<Window x:Class="TaskbarDemo.MainWindow"
        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:TaskbarDemo"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <RoutedCommand x:Key="ApproveCommand"/>
        <local:ProportionConverter x:Key="ProportionConverter"/>
        <local:StateConverter x:Key="StateConverter"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource ApproveCommand}" Executed="CommandBinding_Executed"/>
    </Window.CommandBindings>
    <Window.TaskbarItemInfo>
        <TaskbarItemInfo ProgressValue="{Binding Count, Converter={StaticResource ProportionConverter}, ConverterParameter=10>
            <TaskbarItemInfo.ProgressState>
                <MultiBinding Converter="{StaticResource StateConverter}">
                    <Binding Path="Count"/>
                    <Binding Path="IsError"/>
                </MultiBinding>
            </TaskbarItemInfo.ProgressState>
        </TaskbarItemInfo>
    </Window.TaskbarItemInfo>
    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <Button Width="200" Height="22" Content="Do 10 Things with errors" Command="{StaticResource ApproveCommand}" CommandParameter="ERROR" Foreground="Red"/>
            <TextBlock Text="{Binding Count, StringFormat='{}{0} things done.'}">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Setter Property="Visibility" Value="Visible"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Count}" Value="0">
                                <Setter Property="Visibility" Value="Hidden"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </StackPanel>
    </Grid>
</Window>
  
The code behind exposes two properties - Count and IsError. When the user clicks a button it launches a background process that increments Count from 0 to 10 and back to zero which causes the progress bar to update accordingly. If the "with errors" button was clicked, IsError is set true part way through the process.

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

namespace TaskbarDemo
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private BackgroundWorker bw = new BackgroundWorker();
        private int _Count = 0;

        public int Count
        {
            get { return _Count; }
            set
            {
                _Count = value;
                PropChanged("Count");
            }
        }

        private bool _IsError = false;
        public bool IsError
        {
            get { return _IsError; }
            set
            {
                _IsError = value;
                PropChanged("IsError");
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            bw.DoWork += Bw_DoWork;
        }
        private void Bw_DoWork(object sender, DoWorkEventArgs e)
        {
            Count = 0;
            IsError = false;
            while (Count < 10)
            {
                Thread.Sleep(500);
                Count++;
                if (e.Argument != null && Count == 5 && e.Argument.ToString() == "ERROR") IsError = true;
            }
            Thread.Sleep(500);
            Count = 0;
        }

        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (!bw.IsBusy) bw.RunWorkerAsync(e.Parameter);
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void PropChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
}

There are some converters used so I can drive all the functionality from the Count property. Add a new class to the project and call it Converters. PropertionConverter is used to convert the Count to a value between 0 and 1 which controls the width of the progress bar. StateConverter uses the Count and IsError to determine the color and visibility of the progress bar. The code looks like this.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace TaskbarDemo
{
    class ProportionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            double MaxValue = 1;
            double NewValue = 0;
            double.TryParse((string)parameter, out MaxValue);
            double.TryParse(value.ToString(), out NewValue);
            return NewValue / MaxValue;
        }

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

    class StateConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            int NewValue = 0;
            bool IsError = false;
            int.TryParse(values[0].ToString(), out NewValue);
            bool.TryParse(values[1].ToString(), out IsError);
            if (NewValue < 10)
                if (IsError)
                    return System.Windows.Shell.TaskbarItemProgressState.Error;
                else
                    return System.Windows.Shell.TaskbarItemProgressState.Normal;
            else
                return System.Windows.Shell.TaskbarItemProgressState.None;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Clicking the [Do 10 Things] button will display a progress bar like this.


Clicking the [Do 10 Things with error] button causes the progress bar to change color part way through processing.


When the Things are completed the ProgressState is returned to None which causes the progress bar to disappear.

Overlay

The overlay is a small png icon displayed over the task bar button. I will bind it to IsModified via a converter. I might use it to remind the user that the page they are working on is dirty and needs to be saved. In this example, we will simply us it to indicate that a check box is checked.

Add an Icons folder to the project and then add a png icon called Save.png. I found a red floppy disk on Google Image that I imported. The png gets scaled when you use it, but it needs to be roughly square.


Add a converter that converts from a boolean to a file path.
class OverlayConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string Path = parameter.ToString();
        bool IsModified = (bool)value;
        return IsModified ? Path : "";
    }

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

You can bind the image file's path to the TaskbarItemInfo.Overlay property via the new converter. Alter the definition of TaskbarItemInfo in Window.xaml.

<TaskbarItemInfo ProgressValue="{Binding Count, Converter={StaticResource ProportionConverter}, ConverterParameter=10}" Overlay="{Binding IsModified, Converter={StaticResource OverlayConverter}, ConverterParameter='Icons\\Save.png'}"  >

Add a reference to the overlay converter to the Windows.Resources.

<Window.Resources>
    <RoutedCommand x:Key="ApproveCommand"/>
    <local:ProportionConverter x:Key="ProportionConverter"/>
    <local:StateConverter x:Key="StateConverter"/>
    <local:OverlayConverter x:Key="OverlayConverter"/>
</Window.Resources>

Add a checkbox to the stack panel.

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <CheckBox Content="Page Modified?" IsChecked="{Binding IsModified}"/>
    <Button Width="200" Height="22" Content="Do 10 Things" Command="{StaticResource ApproveCommand}"/>
    <Button Width="200" Height="22" Content="Do 10 Things with errors" Command="{StaticResource ApproveCommand}" CommandParameter="ERROR" Foreground="Red"/>

Add the IsModified property to MainWindow.xaml.cs

private bool _IsModified = false;
public bool IsModified
{
    get { return _IsModified; }
    set
    {
        _IsModified = value;
        PropChanged("IsModified");
    }
}

Now run the application and watch the task bar button when you check the check box.



Here's the complete project.
TaskbarDemo.zip

No comments:

Post a Comment