How to write a DataTemplate with some functionality as a resource

I have some ListBoxes in my application which will contain contacts of a phonebook. I want all these ListBoxes to have a DataTemplate as their ItemsTemplates with some functionality like, edit, remove and view as the code below shows:

<DataTemplate x:Key="ContactItemTemplate">
    <TextBlock Foreground="Black" Background="{StaticResource DataTemplateBackgroundBrush}" Padding="5,10" Margin="4,3">
        <TextBlock.Text>
            <MultiBinding StringFormat="{}{0} {1}">
                <Binding Path="FirstName"/>
                <Binding Path="LastName"/>
            </MultiBinding>
        </TextBlock.Text>
        <TextBlock.ContextMenu>
            <ContextMenu FontFamily="B Yekan">
                <MenuItem Header="Edit" Click="btn_EditContact_Click"/>
                <MenuItem Header="Delete" Click="btn_DeleteContact_Click"/>
                <MenuItem Header="View" Click="btn_EditContact_Click"/>
            </ContextMenu>
        </TextBlock.ContextMenu>
    </TextBlock>
</DataTemplate>

It can't be written as a style in a ResourceDictionary and be added to the controls since eventhandlers can not be presented in ResourceDictionaries. So one way is to copy this template and it's handlers in every Window/Page that have a Contact ListBox like below:

<Page.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/Styles/Controls_Style.xaml"/>
        </ResourceDictionary.MergedDictionaries>
        <DataTemplate x:Key="AttachmentsTemplate">
            <Border Height="150" Width="120" BorderThickness="1" BorderBrush="{StaticResource DefaultBorderBrush}" Margin="2">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Bottom" TextWrapping="WrapWithOverflow"/>
                    <Image Grid.Row="1" Source="{Binding Image}" Margin="5,0"/>
                </Grid>
            </Border>
        </DataTemplate>
    </ResourceDictionary>
</Page.Resources>

Is there any other way that I can write a DataTemplate with some functionality once and use it any where I want? Should I write a UserControl instead of a DataTemplate?

Upvotes: 4

Views: 178

Answers (2)

Petter Hesselberg
Petter Hesselberg

Reputation: 5498

You can have event handlers in your dictionary, as long as they are part of the dictionary -- i.e, the dictionary has some code behind it. This is analogous to Pages and UserControls; you need to add an x:Class attribute. For example, Styles.xml might look like so:

<ResourceDictionary
    x:Class="WPF.Styles"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1">
    <DataTemplate x:Key="ContactItemTemplate" DataType="local:Person">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <TextBlock
                Grid.Column="0"
                Foreground="Black"
                Background="Yellow"
                Padding="5,10"
                Margin="4,3"
                Text="{Binding Name}"
                >
                <TextBlock.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Edit!" Click="btn_EditContact_Click"/>
                        <MenuItem Header="Delete!" Click="btn_DeleteContact_Click"/>
                        <MenuItem Header="View!" Click="btn_EditContact_Click"/>
                    </ContextMenu>
                </TextBlock.ContextMenu>
            </TextBlock>
            <Button
                Grid.Column="1"
                Content="Edit"
                Click="btn_EditContact_Click"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

...with a corresponding Styles.cs (note the partial):

public sealed partial class Styles
{
    private void btn_EditContact_Click(object sender, EventArgs args)
    {
        Debug.WriteLine(args);
    }

    private void btn_DeleteContact_Click(object sender, EventArgs args)
    {
        Debug.WriteLine(args);
    }
}

In my window, something like this:

<Grid.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/Styles.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Grid.Resources>

...

<ItemsControl
    ItemsSource="{Binding People}"
    Grid.Row="3"
    ItemTemplate="{StaticResource ContactItemTemplate}" />

For completeness, model object:

public class Person
{
    public string Name { get; }

    public Person(string name)
    {
        Name = name;
    }
}

...and ItemsSource:

public Person[] People { get; } =
    {
        new Person("Donald Duck"),
        new Person("Mickey Mouse"),
        new Person("Darth Vader"),
    };

In general this works fine -- clicking the button, for example, invokes btn_EdxtContact_Click. Sadly, there's a hitch with your scenario involving the context menu of a TextBox; details and possible workarounds in this post.


Update: On ContextMenus and templates

A small piece of additional info: The comments on this article has some discussion on ContextMenu templating that might be illuminating.

...and one more TextBox ContextMenu link that might prove useful.

Upvotes: 1

Ilan
Ilan

Reputation: 2782

please try the next solution:

Xaml code:

<Window x:Class="DataTemplateInResDectReuseHelpAttempt.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:dataTemplateInResDectReuseHelpAttempt="clr-namespace:DataTemplateInResDectReuseHelpAttempt"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <dataTemplateInResDectReuseHelpAttempt:MainDataContext/>
</Window.DataContext>
<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="ContactsDataTemplateResourceDictionary.xaml"></ResourceDictionary>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>
<Grid>
    <ListBox ItemsSource="{Binding BaseModels}">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <ContentControl Content="{Binding }" ContentTemplate="{StaticResource ContactItemTemplate}"></ContentControl>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
</Grid></Window>

ViewModel and model

    class MainDataContext
{
    private ICommand _viewCommand;
    private ICommand _deleteCommand;
    private ICommand _editCommand;

    public MainDataContext()
    {
        BaseModels = new ObservableCollection<BaseModel>
        {
            new BaseModel(EditCommand, DeleteCommand, ViewCommand){LastName = "Omar", FirstName = "Khayyam"},
            new BaseModel(EditCommand, DeleteCommand, ViewCommand){LastName = "Chekhov", FirstName = "Anton"},
            new BaseModel(EditCommand, DeleteCommand, ViewCommand){LastName = "Lau", FirstName = "Meir"},
        };
    }

    public ICommand ViewCommand
    {
        get { return _viewCommand ?? (_viewCommand = new RelayCommand<object>(View)); }
    }

    private void View(object obj)
    {

    }

    public ICommand DeleteCommand
    {
        get { return _deleteCommand ?? (_deleteCommand = new RelayCommand<object>(Delete)); }
    }

    private void Delete(object obj)
    {

    }

    public ICommand EditCommand
    {
        get { return _editCommand ?? (_editCommand = new RelayCommand<object>(Edit)); }
    }

    private void Edit(object obj)
    {

    }

    public ObservableCollection<BaseModel> BaseModels { get; set; } 
}

public class BaseModel:BaseObservableObject
{
    private string _firstName;
    private string _lastName;
    private readonly ICommand _editCommand;
    private readonly ICommand _deleteCommand;
    private readonly ICommand _viewCommand;

    public BaseModel(ICommand editCommand, ICommand deleteCommand, ICommand viewCommand)
    {
        _editCommand = editCommand;
        _deleteCommand = deleteCommand;
        _viewCommand = viewCommand;
    }

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged();
        }
    }

    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged();
        }
    }

    public ICommand EditCommand
    {
        get { return _editCommand; }
    }

    public ICommand DeleteCommand
    {
        get { return _deleteCommand; }
    }

    public ICommand ViewCommand
    {
        get { return _viewCommand; }
    }
}

ResourceDictionary code:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="DataTemplateBackgroundBrush" Color="Azure"/>
<DataTemplate x:Key="ContactItemTemplate">
    <TextBlock Foreground="Black" Background="{StaticResource DataTemplateBackgroundBrush}" Padding="5,10" Margin="4,3">
        <TextBlock.Text>
            <MultiBinding StringFormat="{}{0} {1}">
                <Binding Path="FirstName"/>
                <Binding Path="LastName"/>
            </MultiBinding>
        </TextBlock.Text>
        <TextBlock.ContextMenu>
            <ContextMenu FontFamily="B Yekan">
                <MenuItem Header="Edit" Command="{Binding EditCommand}" CommandParameter="{Binding }"/>
                <MenuItem Header="Delete" Command="{Binding DeleteCommand}" CommandParameter="{Binding }"/>
                <MenuItem Header="View" Command="{Binding ViewCommand}" CommandParameter="{Binding }"/>
            </ContextMenu>
        </TextBlock.ContextMenu>
    </TextBlock>
</DataTemplate></ResourceDictionary>

I'll glad to help if you will have problems with the code.

Regards.

Upvotes: 2

Related Questions