views:

87

answers:

2

I'm trying to decorate an ItemsControl so that each item will have a Delete button that floats over the item's content under a specific condition inspired by somewhat by the iPhone UI. I have a few ways in mind of how I can approach this but I could use some guidance from other WPF folks who might have a better idea about how this would best be done. Below is a mock-up image to help express what I'm trying to do.

Example mock-up

My current thoughts are to try to attempt this by only using XAML only using styles, templates, and maybe attached properties if necessary. The idea is to create conditional DataTemplate for the items control that would somehow wrap the original content with an adorner that contains my Delete button. In order to have a state on my ItemsControl to know whether I'm in a delete mode or not I'm thinking maybe to create an attached property that I can then set in a variety of ways such as binding it to the state of a toggle button or a checkbox for example.

At this point the concept makes sense but the fine details are a little unclear to me whether using the ItemTemplate is the best move since in some cases an ItemTemplate may already exist for a given ItemsControl and I do not want to overwrite it but instead would only want to wrap it (if that makes sense). I'm thinking that if I pull if off right I should be able to apply this to any items control by specifying the style and maybe an attached property.

If anybody could help illustrate these finer details or offer any better suggestions on how I could go about please share.

A: 

I've created the following mock-up to illustrate one easy way this can be done, using a single DataTemplate a few bindings. You are definitely on the right track.

<Window x:Class="TestWpfApplication.Foods"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="Foods" ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
    <local:BoolToVisConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<StackPanel Background="LightGray">
    <ToggleButton Name="EditModeToggle" Content="Edit" HorizontalAlignment="Right" FontFamily="Arial" Padding="4" 
                  Background="#7FA4E6" Foreground="White" BorderBrush="Black" Width="60" Margin="5,5,5,0"/>
    <ListBox ItemsSource="{Binding Items}" 
             Background="#999" BorderBrush="Black" Margin="5">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Border CornerRadius="8" Background="#3565BC" Padding="8" 
                        BorderBrush="#333" BorderThickness="1" Margin="2" Width="255">
                    <StackPanel Orientation="Horizontal">
                        <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=RemoveItemCommand}"
                                Visibility="{Binding ElementName=EditModeToggle, Path=IsChecked, Converter={StaticResource BoolToVisConverter}}">
                            <Button.Content>
                                <TextBlock Foreground="Red" Text="X" FontWeight="Bold"/>
                            </Button.Content>
                        </Button>
                        <TextBlock Text="{Binding}" Margin="12,0" Foreground="#AAA" VerticalAlignment="Center"
                                   FontSize="14" FontWeight="Bold" FontFamily="Arial"/>
                    </StackPanel>
                </Border>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</StackPanel>

You don't need much code to make this work, but there is still some code-behind, mainly for the RoutedCommand:

public partial class Foods : Window
{
    private ObservableCollection<String> items = new ObservableCollection<string>();
    private RoutedCommand removeItemCommand = new RoutedCommand();

    public Foods()
    {
        InitializeComponent();

        items.Add("Ice Cream");
        items.Add("Pizza");
        items.Add("Apple");

        CommandBindings.Add(new CommandBinding(removeItemCommand, ExecutedRemoveItem));
    }

    public ObservableCollection<String> Items
    {
        get { return items; }
    }

    public RoutedCommand RemoveItemCommand
    {
        get { return removeItemCommand; }
    }

    private void ExecutedRemoveItem(object sender, ExecutedRoutedEventArgs e)
    {
        DependencyObject container = 
            ItemsControl.ContainerFromElement(e.Source as ItemsControl, e.OriginalSource as DependencyObject);
        ListBoxItem item = container as ListBoxItem;
        items.Remove(item.Content as String);
    }
}

And the result could be made far more visually appealing, obviously, but I nearly duplicated your idea haha:

alt text

Charlie
+1  A: 

Normally you would add something like this in an ItemContainerStyle so as not to mess with the DataTemplate itself every time you want to apply this. While this is simple with a ListBox where you can modify the Template for ListBoxItem, unfortunately the base ItemsControl uses just a ContentPresenter as its container and so can't be templated in the same way.

If you really want this to be reusable I would suggest that you wrap it up into a new custom ItemsControl that you can drop in to replace a standard one without having to modify the specific DataTemplate being used. This will also allow you to wrap up the property that you would otherwise create externally as an attached prop and the delete command in the control itself.

Obviously the delete logic and visual styling have not been done here but this should get you started:

public class DeleteItemsControl : ItemsControl
{
    public static readonly DependencyProperty CanDeleteProperty = DependencyProperty.Register(
        "CanDelete",
        typeof(bool),
        typeof(DeleteItemsControl),
        new UIPropertyMetadata(null));

    public bool CanDelete
    {
        get { return (bool)GetValue(CanDeleteProperty); }
        set { SetValue(CanDeleteProperty, value); }
    }

    public static RoutedCommand DeleteCommand { get; private set; }

    static DeleteItemsControl()
    {
        DeleteCommand = new RoutedCommand("DeleteCommand", typeof(DeleteItemsControl));
        DefaultStyleKeyProperty.OverrideMetadata(typeof(DeleteItemsControl), new FrameworkPropertyMetadata(typeof(DeleteItemsControl)));
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
        return new DeleteItem();
    }

    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return item is DeleteItem;
    }
}

public class DeleteItem : ContentControl
{
    static DeleteItem()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(DeleteItem), new FrameworkPropertyMetadata(typeof(DeleteItem)));
    }

}

This would go in Generic.xaml or you could just apply them like normal styles in your app:

<Style TargetType="{x:Type local:DeleteItemsControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:DeleteItemsControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ItemsPresenter/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="{x:Type local:DeleteItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:DeleteItem}">
                <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                    <DockPanel>
                        <Button Command="local:DeleteItemsControl.DeleteCommand" Content="X" HorizontalAlignment="Left" VerticalAlignment="Center"
                            Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:DeleteItemsControl}}, Path=CanDelete, Converter={StaticResource BooleanToVisibilityConverter}}"/>
                        <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                    </DockPanel>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
John Bowen