Author Archive Mel

ByMel

WPF: Time Control 12 Hour Format

Time Control 12 Hour Format
Time Control 12 Hour Format

I’ve spent a bit of time scouring the internet for a WPF Time Control that supports 12-hour format. There are several examples out there that are in 24-hour format, but I haven’t seen any for 12-hour format, so I wrote one. I began with some code located here and then modified it extensively to work for my situation. I didn’t need to display seconds, however it would be pretty easy to add them.

This control supports a wide range of keyboard input. You can enter the value for Hour, Minute, and Part of Day by either using the up/down arrow keys or by typing in numbers and letters. If you have any questions/suggestions/praise/opinions please feel free to leave a comment!

XAML:

<UserControl x:Class="CGS.TimeControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:CGS"
             Height="Auto" Width="Auto" x:Name="UserControl">
    <UserControl.Resources>
        <local:MinuteSecondToStringConverter x:Key="minuteSecondConverter" />
    </UserControl.Resources>
 
    <Border BorderBrush="Black" BorderThickness="1" CornerRadius="1">
        <Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" Margin="2" Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.2*"/>
                <ColumnDefinition Width="0.05*"/>
                <ColumnDefinition Width="0.2*"/>
                <ColumnDefinition Width="0.05*"/>
                <ColumnDefinition Width="0.2*"/>
            </Grid.ColumnDefinitions>
 
            <Grid x:Name="hour" Focusable="True" KeyDown="Down" GotFocus="Grid_GotFocus" LostFocus="Grid_LostFocus" MouseDown="Grid_MouseDown">
                <TextBlock TextWrapping="Wrap" Text="{Binding Path=Hours, ElementName=UserControl, Mode=Default}"
                 TextAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ElementName=UserControl, Path=FontSize}"/>
            </Grid>
 
            <Grid  Grid.Column="1">
                <TextBlock  x:Name="sep1" TextWrapping="Wrap" VerticalAlignment="Center" Background="{x:Null}"
                  FontSize="{Binding ElementName=UserControl, Path=FontSize}" Text=":" TextAlignment="Center"/>
            </Grid>
 
            <Grid  Grid.Column="2" x:Name="min" Focusable="True" KeyDown="Down" GotFocus="Grid_GotFocus" LostFocus="Grid_LostFocus" MouseDown="Grid_MouseDown">
                <TextBlock  TextWrapping="Wrap" Text="{Binding Path=Minutes, ElementName=UserControl, Mode=Default, Converter={StaticResource minuteSecondConverter}}"
                  TextAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ElementName=UserControl, Path=FontSize}"/>
            </Grid>
 
            <Grid  Grid.Column="4" Name="half" Focusable="True" KeyDown="Down" GotFocus="Grid_GotFocus" LostFocus="Grid_LostFocus" MouseDown="Grid_MouseDown">                
                <TextBlock TextWrapping="Wrap" Text="{Binding Path=DayHalf, ElementName=UserControl, Mode=Default}"
                 TextAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ElementName=UserControl, Path=FontSize}"/>
            </Grid>
 
        </Grid>
    </Border>
</UserControl>

Code:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Data;
 
namespace CGS
{
    /// <summary>
    /// Interaction logic for TimeControl.xaml
    /// </summary>
    public partial class TimeControl : UserControl
    {
        const string amText = "am";
        const string pmText = "pm";
 
        static SolidColorBrush brBlue = new SolidColorBrush(Colors.LightBlue);
        static SolidColorBrush brWhite = new SolidColorBrush(Colors.White);
 
        DateTime _lastKeyDown;
 
        public TimeControl()
        {
            InitializeComponent();
 
            _lastKeyDown = DateTime.Now;
        }
 
        public TimeSpan Value
        {
            get { return (TimeSpan)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(TimeSpan), typeof(TimeControl),
        new UIPropertyMetadata(DateTime.Now.TimeOfDay, new PropertyChangedCallback(OnValueChanged)));
 
        private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            TimeControl control = obj as TimeControl;
 
            TimeSpan newTime = ((TimeSpan)e.NewValue);
 
            int timehours = newTime.Hours;
            int hours = timehours % 12;
            hours = (hours > 0) ? hours : 12;
 
            control._hours = newTime.Hours;
            control.Hours = hours;
            control.Minutes = ((TimeSpan)e.NewValue).Minutes;
            control.DayHalf = ((timehours - 12) >= 0) ? pmText : amText;
 
        }
 
        private int _hours;
        public int Hours
        {
            get { return (int)GetValue(HoursProperty); }
            set { SetValue(HoursProperty, value); }
        }
        public static readonly DependencyProperty HoursProperty =
        DependencyProperty.Register("Hours", typeof(int), typeof(TimeControl),
        new UIPropertyMetadata(0));
 
        public int Minutes
        {
            get { return (int)GetValue(MinutesProperty); }
            set { SetValue(MinutesProperty, value); }
        }
        public static readonly DependencyProperty MinutesProperty =
        DependencyProperty.Register("Minutes", typeof(int), typeof(TimeControl),
        new UIPropertyMetadata(0));
 
 
        public string DayHalf
        {
            get { return (string)GetValue(DayHalfProperty); }
            set { SetValue(DayHalfProperty, value); }
        }
        public static readonly DependencyProperty DayHalfProperty =
        DependencyProperty.Register("DayHalf", typeof(string), typeof(TimeControl),
        new UIPropertyMetadata(amText));
 
        private void Down(object sender, KeyEventArgs args)
        {
            bool updateValue = false;
 
            if (args.Key == Key.Up || args.Key == Key.Down)
            {
                switch (((Grid)sender).Name)
                {
                    case "min":
                        if (args.Key == Key.Up)
                            if (this.Minutes + 1 > 59)
                            {
                                this.Minutes = 0;
                                goto case "hour";
                            }
                            else
                            {
                                this.Minutes++;
                            }
                        if (args.Key == Key.Down)
                            if (this.Minutes - 1 < 0)
                            {
                                this.Minutes = 59;
                                goto case "hour";
                            }
                            else
                            {
                                this.Minutes--;
                            }
                        break;
 
                    case "hour":
                        if (args.Key == Key.Up)
                            this._hours = (_hours + 1 > 23) ? 0 : _hours + 1;
                        if (args.Key == Key.Down)
                            this._hours = (_hours - 1 < 0) ? 23 : _hours - 1;
                        break;
 
                    case "half":
                        this.DayHalf = (this.DayHalf == amText) ? pmText : amText;
 
                        int timeHours = this.Hours;
                        timeHours = (timeHours == 12) ? 0 : timeHours;
                        timeHours += (this.DayHalf == amText) ? 0 : 12;
 
                        _hours = timeHours;
                        break;
                }
 
                updateValue = true;
 
                args.Handled = true;
            }
            else if ((args.Key >= Key.D0 && args.Key <= Key.D9) || (args.Key >= Key.NumPad0 && args.Key <= Key.NumPad9))
            {
                int keyValue = (int)args.Key;
                int number = 0;
 
                number = keyValue - ((args.Key >= Key.D0 && args.Key <= Key.D9) ?
                                        (int)Key.D0 :
                                        (int)Key.NumPad0
                                    );
 
                bool attemptAdd = (DateTime.Now - _lastKeyDown).TotalSeconds < 1.5;
 
                switch (((Grid)sender).Name)
                {
                    case "min":
                        if (attemptAdd)
                        {
                            number += this.Minutes * 10;
 
                            if (number < 0 || number >= 60)
                            {
                                number -= this.Minutes * 10;
                            }
                        }
 
                        this.Minutes = number;
                        break;
 
                    case "hour":
                        if (attemptAdd)
                        {
                            number += this.Hours * 10;
 
                            if (number < 0 || number >= 13)
                            {
                                number -= this.Hours * 10;
                            }
                        }
 
                        number = (number == 12) ? 0 : number;
                        number += (this.DayHalf == amText) ? 0 : 12;
 
                        _hours = number;
                        break;
 
                    default:
                        break;
                }
 
                updateValue = true;
 
                args.Handled = true;
            }
            else if (args.Key == Key.A || args.Key == Key.P)
            {
                if (((Grid)sender).Name == "half")
                {
                    this.DayHalf = (args.Key == Key.A) ? amText : pmText;
 
                    updateValue = true; 
                }
            }
 
            if (updateValue)
            {
                this.Value = new TimeSpan(_hours, this.Minutes, 0);
            }
 
            _lastKeyDown = DateTime.Now;
        }
 
        private void Grid_GotFocus(object sender, RoutedEventArgs e)
        {
            var grd = sender as Grid;
 
            grd.Background = brBlue;
        }
 
        private void Grid_LostFocus(object sender, RoutedEventArgs e)
        {
            var grd = sender as Grid;
 
            grd.Background = brWhite;
        }
 
        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var grd = sender as Grid;
 
            grd.Focus();
        }
    }
 
 
    public class MinuteSecondToStringConverter : IValueConverter
    {
        #region IValueConverter Members
 
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
            {
                if (value is int)
                {
                    return ((int)value).ToString("00");
                }
            }
 
            return string.Empty;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
            {
                if (value is string)
                {
                    int number;
                    if (int.TryParse(value as string, out number))
                    {
                        return number;
                    }
                }
            }
 
            return 0;
        }
 
        #endregion
    }
}

And here’s an example of how to use it:

<cgs:TimeControl x:Name="timeControl" Margin="15,0,0,0" Height="25" Width="70" xmlns:cgs="clr-namespace:CGS" />
// Set the time of the control
this.timeControl.Value = DateTime.Now.TimeOfDay;
 
// Get the time from the control
TimeSpan time = this.timeControl.Value;
ByMel

ProgressStream: A Stream with Read and Write events

In my experience with WCF I’ve implemented several solutions that leverage the message streaming capabilities present in the framework. Specifically in a client/server scenario when either side needs to transfer a file to the other side it is really convenient to be able to just pass a FileStream to a WCF service call and have the server read bytes from it and write them to disk as it pleases.

However, in this exact scenario, while the server can monitor and control how the bytes are read from the stream and track the progress, the client has no inherent way of determining how much of the file has been read across the network by the server. It would be really useful if the client could track this upload progress and display a progress bar or percentage to the user.

Well, I’ve created one of probably many possible solutions to this dilemma. I’ve written a class that inherits from Stream and exposes some events that are raised when bytes are read or written to/from the stream. These events provide information for how many bytes were read from the stream, what the current position is in the stream, and what the stream’s total length is.

The ProgressStream, as I call it, accepts any Stream object and encapsulates it, forwarding all regular stream calls to the underlying object. With this you can use the ProgressStream in conjunction with any other stream in .Net and leverage the added functionality.

Well enough explanation, here’s the code:

ProgressStream.cs (C#)
ProgressStream.vb (VB.Net)

And here’s a simple example in C# of how to use it:

using System;
using System.IO;
using CGS;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var test = File.OpenRead(@"C:\Test\Test.doc");
 
            var pStream = new ProgressStream(test);
            pStream.BytesRead += 
                new ProgressStreamReportDelegate(pStream_BytesRead);
 
            int bSize = 4320;
            byte[] buffer = new byte[bSize];
            while (pStream.Read(buffer, 0, bSize) &gt; 0) { }
 
            Console.ReadKey();
        }
 
        static void pStream_BytesRead(object sender, 
                                      ProgressStreamReportEventArgs args)
        {
            Console.WriteLine(
                string.Format("{2} bytes moved | {0} of {1} total", 
                    args.StreamPosition, 
                    args.StreamLength, 
                    args.BytesMoved
                )
           );
        }
    }
}
ByMel

Call of Duty: Modern Warfare 2 Multiple Profiles

Modern Warfare 2 Launcher

I created this launcher to provide multiple profiles for the single player game of Call of Duty: Modern Warfare 2. As there are multiple ways to acquire this game and install it, this may not work for everyone.

Instructions:

  1. Download the zip file here
  2. Extract the contents to the install directory for Modern Warfare 2
  3. Create a shortcut on your desktop for MW2 Loader.exe and use it instead of launching the game directly.
  4. When you click “Launch” and you’re asked to select a program or shortcut to run, navigate to the shortcut you would normally use to launch the single player game and select it. This will ensure that the game is run through Steam.
  5. If for some reason you need to re-select a program or shortcut to run, you can do this by resetting the Loader. To do this you need to delete the profiles.xml file located in:
    (XP) C:\Documents and Settings\All Users\Application Data\MW2_Loader\
    (Vista) C:\Program Data\MW2_Loader\
    (Win7) C:\ProgramData\MW2_Loader\
  6. If you don’t already have the Microsoft .Net Framework 3.5 installed on your system, you will need to download it here

Here’s a picture of what the game directory should look like after you’ve extracted the file:
mw2_dir

Note: This only works for the PC version of the game and as far as I know only changes your Single Player profile, which includes all game settings and saved games.

If you have any questions or comments please feel free to post them below. Feedback and praise is always welcome!

Updates:

2009-11-16:

  • Fixed System.IO.FileFormatException: The image format is unrecognized bug.

2009-11-15:

  • Fixed bug with new profiles disappearing when not launched.
  • Changed program icon in an attempt to better support Vista and WPF.
  • Implemented ability to choose program or shortcut to launch after loading a profile. You should use this to select the shortcut you regularly use to launch the game with Steam.
ByMel

WPF: CheckBox as GroupBox Header

This is a followup post to the one I wrote on Enabling Controls with a CheckBox. In that post we created some XAML that would enable/disable controls in the GUI based on the IsChecked property of a checkbox.

Here’s an enhancement to that:

Checkbox as header for Groupbox

So what we’re doing is giving the controls on our dialog a nice visual grouping letting the user know that they’re associated. The GroupBox element has long existed to fulfill this need. However, with this little XAML change it also doubles as a sort of access control for the items it contains.

Here’s the XAML:

<groupbox padding="5" horizontalalignment="Stretch">
    <groupbox.header>
        <checkbox x:name="chkEnableBackup">Run Backup Sets</checkbox>
    </groupbox.header>
 
    <stackpanel isenabled="{Binding ElementName=chkEnableBackup, Path=IsChecked}">
        <stackpanel orientation="Horizontal">
            <label margin="12,0,0,0">Run backup every</label>
            <combobox width="70" selectedindex="0">
                <comboboxitem>Minute</comboboxitem>
                <comboboxitem>Hour</comboboxitem>
                <comboboxitem>Day</comboboxitem>
            </combobox>
        </stackpanel>
        <stackpanel margin="12,10,0,0">
            <label>Path to Backup:</label>
            <textbox width="200" margin="5,0,0,0">
        </textbox></stackpanel>
    </stackpanel>
</groupbox>

So all we’ve done here is add the CheckBox to the <GroupBox.Header> element of the GroupBox. Pretty slick!

This little technique is complete UI Candy and in my opinion illustrates one of the many powerful features of WPF: the ability to customize the GUI in any way you want, down to any level!

ByMel

WPF: Creating a Pop Up Button Style

So I’ve been getting pretty fancy with my GUI development, and today I wanted to design a button that would appear as just flat text until you hovered over it with the mouse, at which point it would “pop up” and look like an actual button.

Regular Button

'Flat' Button

The following solution only works when run on a system with a Windows Vista/7 theme enabled, such as Aero or Basic. It will not work when run with the Windows Classic theme.

The ‘Flat’ Pop Up Button was achieved by simply setting the background and border to transparent. Here’s the XAML:

<Button Content="Click Me!" Background="Transparent" BorderBrush="Transparent"/>

The next step was to make it so that when the mouse hovers over the button it “pops up” visually and looks like your standard button. At first I tried several methods for accomplishing this using triggers to set the style of the button which were all turning out to be pretty messy since I’m not the best graphical designer.

But silly me, I didn’t realize that in this case I didn’t have to do anything special at all! I could just leave the button as it is in the XAML above and the baked in triggers for the Button would still kick in and change the appearance when the mouse hovered over it.

Here’s what that looks like:

Pop Up Button

It looks pretty cool in spots where you want a nice, Aero style button, but if the user isn’t interacting with it you don’t want all those pixels distracting from the clean whiteness that is your polished application.