WPF Tidbits

Here’s a couple of ‘how do you that in wpf?’ that may save you an hour or two. There’s nothing particularly astounding in any of them but implementation is different enough from the WinForm counterparts that it may not be intuitive enough.

Modal Dialog

There is no option for a dialog from the ‘Add… New Item’ using the solution explorer in Visual Studio. Instead you have to remember that a dialog is nothing more than a window. And there is an option for ‘Window(WPF)’. You create a dialog the same way you would create a User Control and then invoke it as was done under WinForm, using the ShowDialog method.
The sample code (found in the downloads page) provides a handler for the OK button and you can set the ‘IsCancel’ property for the Cancel button so that no handler is required. The only other thing to note is that there is no equivalence to DialogResult that was used in WinForms. There is a DialogResult parameter but it is not the same. Here, the DialogResult is a nullable bool parameter which essentially translates to OK or Cancel from the dialog. Of course you can implement your own custom version of DialogResult by using a property if you needed to .

wpfdialog

 

The sample project provides all the details you’ll need but here is the XAML for the dialog.
 <Window x:Class="WpfDialogTestApp.WpfDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Wpf Dialog" Height="103" Width="257">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock VerticalAlignment="Center">Enter#</TextBlock>
        <TextBox Name="textNumber" Grid.Column="1"></TextBox>
        <Button Grid.Row="1" Name="buttonOK" Click="buttonOK_Click">OK</Button>
        <Button Grid.Column="1" Grid.Row="1" IsCancel="True">Cancel</Button>
    </Grid>
</Window> 

 Horizontal Listbox

It may not be the usual case but sometimes it will be just what’s required. It may simply be that the layout is more pleasing displaying the items horizontally or that what you are trying to display is more intuitive if it were laid out horizontally. Well at least that was the case for me. So here it is.
There is nothing magical, the magic is already built in to WPF and the fact that each item in a listbox can be a custom User Control.  Obviously the requirements will dictate if it’s appropriate, but I can see a lot of opportunities if we look at things from a different angle.
The sample project provides all the source code for the implementation. The code is also implemented following the MVVM pattern which fits the bill very nicely. Each listbox item could very easily be self contained, initializing itself and responding to user actions. The sample project utilizes event delegates for communication but a looser approach would be to use an event aggregator as provided with CAL.

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:v="clr-namespace:HorzListboxTestApp.Views"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="HorzListboxTestApp.Views.MainWindowView"
    MinHeight="70" MinWidth="300">
    <UserControl.Resources>
    <Style TargetType="ListBox">
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"
                          VerticalAlignment="Center"
                          HorizontalAlignment="Center"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Border BorderThickness="2">
                        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                            <v:LBItemView/>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="80"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <ListBox Grid.Column="1" Grid.RowSpan="2" ItemsSource="{Binding Path=Items}"></ListBox>
        <Label Background="{Binding ContentBrush}"></Label>
        <TextBlock Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Path=SelectedItem}"></TextBlock>
    </Grid>

 MVVM Context Menu

 

As you can see, the listbox can provide a lot information to the user in a very compact format. The key of course is that each item is itself an independent control. Suppose in the above example we had placed a combo box to the left of the listbox and allowed the user to select categories of colors. We would have multiplied the amount of information without losing usability.
As mentioned above, whatever the listbox item represents, its logic is contained within the control and the code does not become more convoluted with the number of items. Let’s take that two steps further.
In this sample project we’re using the same project from the previous section and adding two features to it. One is a simple tooltip and the other is a dynamically generated context menu, again, following the MVVM pattern. Here’s the XAML for the listbox item.
 <UserControl x:Class="HorzListboxTestApp.Views.LBItemView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="50" Width="40">
    <UserControl.Resources>
        <Style x:Key="buttonWithTip" TargetType="{x:Type Button}">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="true">
                    <Setter Property="ToolTip"
                Value="{Binding Path=GetTip}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="ContextMenuItemStyle">
            <Setter Property="MenuItem.Header" Value="{Binding Path=OptionText}"/>
            <Setter Property="MenuItem.Command" Value="{Binding Path=OptionCommand}" />
        </Style>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="20"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Button Background="{Binding ContentBrush}" Grid.ColumnSpan="2" Command="{Binding Path=SelectedCommand}" Style="{StaticResource buttonWithTip}">
            <Button.ContextMenu>
                <ContextMenu ItemContainerStyle="{StaticResource ContextMenuItemStyle}" ItemsSource="{Binding Path=MenuOptions}" />
            </Button.ContextMenu>
        </Button>
        <TextBlock Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding ItemNumber}"></TextBlock>
        <CheckBox Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding Path=ItemChecked, Mode=TwoWay}"></CheckBox>
    </Grid>
</UserControl>

Two points will provide all the clarity we need. ContextMenu has an ItemsSource same as a listbox so we can bind to it and provide the list of items to display. The more important part is the whole point of the context menu, to provide the user with a mechanism to make a selection. So we need a way to identify which item the user selected. Well, if we use the same approach we used for the listbox then we can let each menu item be independent on to itself. In other words, provide a ViewModel for each context menu item. To provide the binding all that’s needed is to define a style for the menu item. The style simply defines where the text for the menu item comes from and how to bind the command that will be executed when a user selects the item. The code above shows how both of these were implemented and the support class is shown below.

      public class MenuOptionItem
    {
        public string OptionText { get; set; }
        public ICommand OptionCommand { get; set; }
        public MenuOptionItem(string optionText)
        {
            this.OptionText = optionText;
            OptionCommand = new DelegateCommand(OptionSelected);
        }
        public void OptionSelected()
        {
            MessageBox.Show("You selected:" + OptionText);
        }
    }

The sample project also shows how you can dynamically create context menus based on the state of the item.

Sortable/Checkable ListView

There are two things that I think would be common requirements for a ListView. One is to be able to sort the items based on one or more columns. And the other is to be able to ‘check’ an item like the CkeckedListbox. Neither of these are provided in the WPF ListView control. Adding a checkbox was pretty simple, sorting was a different story.
 Joel Rumerman wrote a nice article here and I found an implementation which fit nicely in the AvalonControlsLibrary in CodePlex. You can find supporting information there.

 <UserControl x:Class="ListViewTestApp.Views.MainWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:c="clr-namespace:ListViewTestApp.Common"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="200" Width="350">
    <Control.Resources>
        <DataTemplate x:Key="HeaderTemplateArrowUp">
            <DockPanel>
                <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
               <Path x:Name="arrow" StrokeThickness = "1" Fill = "Gray" Data = "M 5,10 L 15,10 L 10,5 L 5,10"/>
            </DockPanel>
        </DataTemplate>
        <DataTemplate x:Key="HeaderTemplateArrowDown">
            <DockPanel>
                <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
                <Path x:Name="arrow" StrokeThickness = "1" Fill = "Gray" Data="M 5,5 L 10,10 L 15,5 L 5,5"/>
            </DockPanel>
        </DataTemplate>
        <DataTemplate x:Key="HeaderTemplateTransparent">
            <DockPanel>
                <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
                <Path x:Name="arrow" StrokeThickness="1" Fill="Transparent" Data="M 5,5 L 10,10 L 15,5 L 5,5"/>
            </DockPanel>
        </DataTemplate> 
        <!--Sort handler that sorts the columns-->
        <c:GridViewSortHandler x:Key="sortHandler"
                                  ColumnHeaderSortedAscendingTemplate="HeaderTemplateArrowUp"
                                  ColumnHeaderSortedDescendingTemplate="HeaderTemplateArrowDown"
                                  ColumnHeaderNotSortedTemplate="HeaderTemplateTransparent" />
        <Style x:Key="OrderItemStyle" TargetType="{x:Type ListViewItem}">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
           <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
        </Style>
    </Control.Resources>
    <Grid>
        <ListView c:GridViewSortHandler.GridViewSortHandler="{StaticResource sortHandler}" Name="listOrders" Width="Auto"
                 ItemContainerStyle="{StaticResource OrderItemStyle}"
              ItemsSource="{Binding Path=OrderItems}">
            <ListView.View>
               <GridView>
                    <GridViewColumn Width="30">
                      <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox IsEnabled="{Binding Path=CheckEnabled}" IsChecked="{Binding Path=ItemChecked}"></CheckBox>
                            </DataTemplate>
                       </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                   <c:SortableGridViewColumn  Width="100"  Header="Date" SortPropertyName="OrderDate"  DisplayMemberBinding="{Binding Path=OrderDate}" />
                   <c:SortableGridViewColumn Width="50"  Header="Order#" SortPropertyName="OrderNumber" DisplayMemberBinding="{Binding Path=OrderNumber}" />
                   <c:SortableGridViewColumn Width="50" Header="Item" SortPropertyName="ItemSKU" DisplayMemberBinding="{Binding Path=ItemSKU}" />
                   <c:SortableGridViewColumn Width="25" Header="Qty" SortPropertyName="ItemQty" DisplayMemberBinding="{Binding Path=ItemQty}" />
                   <c:SortableGridViewColumn Header="Description" SortPropertyName="" DisplayMemberBinding="{Binding Path=Description}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</UserControl>

The sample project provides all the implementation details. Each column is also decorated with a ’sort direction’ arrow indicating ascending/descending sort order. You can extend the code to provide multi-column sort if required as well as custom sort algorithm. You’ll find more information with the AvalonControlsLibrary which by the way includes a number of other useful controls.

One last note on the CheckBox, it’s enabled from code. The reason is that sometimes you want to prevent the user from making the selection twice; maybe the order has already been shipped.

Have fun.

 

1 Comment to “WPF Tidbits”

  1. Twitted by K3n5u — October 23, 2009 @ 12:00 am

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Spam Protection by WP-SpamFree