#1,135 – Alternatives to Creating a Custom Control

You’ll typically use existing WPF controls when creating an application’s user interface.  You can set property values for the control to customize both the look and behavior of the control.

If an existing control doesn’t provide the desired behavior or visual appearance, you can build a custom control.

Creating a custom control involves a fair amount of work.  Before deciding to create a new control, you should consider the alternatives listed below.

  • Use an existing control
    • Modify behavior or appearance using properties
    • Execute code in response to the control’s events
  • Use a control template to change the appearance of an existing control
  • Use a data template to change how bound data will be rendered by the control
  • Use triggers to change something in the control based on an event or changing data
  • Use value converter to customize data provided to the control
  • Use attached property to provide custom behavior

#1,094 – Disabling Editing Operations in a TextBox

You can disable the cut/copy/paste features in a TextBox by handling the CommandManager.PreviewCanExecute event and checking for the associated editing commands.

TextBox has implicit command bindings to support cut, copy, and paste commands.  When the user executes one of these commands, using the context menu or a keyboard shortcut, the associated routed command is executed, with the TextBox as the source.  You can short-circuit the command by intercepting PreviewCanExecute and making sure that CanExecute returns false.

Markup:
        <TextBox Name="txtSomeText"
                 CommandManager.PreviewCanExecute="txtSomeText_PreviewCanExecute"
                 Width="220" Height="25" Margin="10"/>

Code-behind:

        private void txtSomeText_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if ((e.Command == ApplicationCommands.Cut) ||
                (e.Command == ApplicationCommands.Copy) ||
                (e.Command == ApplicationCommands.Paste))
            {
                e.Handled = true;
                e.CanExecute = false;
            }
        }

The editing functions are now disabled. The context menu in the TextBox shows the commands greyed out and corresponding keyboard shortcuts do nothing.

1094-001

#1,043 – Using a DockPanel as the Items Panel for a ListBox

You can replace the default StackPanel used as the items panel for a ListBox with any other panel element.  Below is an example of displaying some news stories in a DockPanel.

Assume that we have a NewsStory class as follow:

    public class NewsStory
    {
        public string Story { get; set; }
        public Brush Color { get; set; }
        public Dock Dock { get; set; }
        public double Rotate { get; set; }

        public NewsStory(string story, Color color, Dock dock, double rotate)
        {
            Story = story;
            Color = new SolidColorBrush(color);
            Dock = dock;
            Rotate = rotate;
        }

        public override string ToString()
        {
            return Story;
        }
    }

We can then create a collection of NewsStory items that we’ll bind to.  Notice that we create a spiral pattern by setting consecutive Dock properties to Bottom/Left/Top/Right.  We also use the Angle property to rotate every other element.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            Stories = new ObservableCollection<NewsStory>
            {
                new NewsStory("Diaper Market Bottoms Out", Colors.AliceBlue, Dock.Bottom, 0.0),
                new NewsStory("Antique Stripper to Display Wares", Colors.AntiqueWhite, Dock.Left, -90.0),
                new NewsStory("Cancer Society Honors Marlboro Man", Colors.Aqua, Dock.Top, 0.0),
                new NewsStory("War Dims Hope for Peace", Colors.Aquamarine, Dock.Right, -90.0)
                // more entries go here..
            };
            RaisePropertyChanged("Stories");

        }

        public ObservableCollection<NewsStory> Stories { get; protected set; }

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

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

In XAML, we set the ItemContainerStyle to do the docking and specify a DockPanel as the ItemsPanel.

        <ListBox ItemsSource="{Binding Stories}">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="DockPanel.Dock" Value="{Binding Dock}"/>
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                </Style>
            </ListBox.ItemContainerStyle>
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <DockPanel IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding Story}"
                           Background="{Binding Color}">
                        <Label.LayoutTransform>
                            <RotateTransform Angle="{Binding Rotate}"/>
                        </Label.LayoutTransform>
                    </Label>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Here’s what the end result looks like:

1044-001

#1,042 – How FlowDirection Affects a StackPanel

Changing the FlowDirection property of a StackPanel changes how it lays out elements when its Orientation is set to Horizontal.

When Orientation is Horizontal and FlowDirection is LeftToRight (the default), each label’s width is set to fit its content.  The label’s height is stretched to fill the container.  Labels are arranged left to right.

1042-001

 

Changing FlowDirection to RightToLeft, the labels are arranged from right to left.

1042-002

 

If Orientation is Vertical and FlowDirection is LeftToRight, the labels’ width now sizes to fit the container and their height is set to match the content.  The labels are arranged from top to bottom in the StackPanel.

1042-003

Changing FlowDirection to RightToLeft when the orientation is Vertical does not change how the labels are arranged.  They are still ordered from top to bottom in the StackPanel.  The content of the labels, however, is now right-aligned, rather than left-aligned.

1042-004

#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

Follow

Get every new post delivered to your Inbox.

Join 366 other followers