views:

413

answers:

2

I have a ItemsControl with a collection of objects. I wan't to be able to click the object and then get a panels with more info.

So I decided to style the DataTemplate for the items in the ItemsControl as a button and that seems to work fine. However I have no idea how to set the click event of this button in the style. It says I should use a EventSetter but I can't get that to work.

Here is the code:

  <Style TargetType="Expander" >
            <Style.Resources>
                <Style TargetType="ItemsControl" >
                    <Setter Property="Template" >
                        <Setter.Value>
                            <ControlTemplate TargetType="ItemsControl">
                                <Border BorderThickness="0,1,0,1" BorderBrush="{StaticResource DarkColorBrush}" >
                                    <ScrollViewer Margin="0" VerticalScrollBarVisibility="Auto"
                                                  Focusable="false">
                                        <StackPanel Margin="2" IsItemsHost="True" />
                                    </ScrollViewer>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                    <Setter Property="ItemTemplate" >
                        <Setter.Value>
                            <DataTemplate DataType="{x:Type data:CompanyViewModel}" >
                                <Button>
                                    <Button.Resources>
                                        <Style TargetType="Button">
                                            <Setter Property="Template">
                                                <Setter.Value>
                                                    <ControlTemplate TargetType="Button">
                                                        <Border Name="Bd" BorderBrush="{StaticResource DarkColorBrush}"
                                                                BorderThickness="1"
                                                                Margin="5"
                                                                CornerRadius="8">

                                                            <Border.Background>
                                                                <!-- Removed for brevity -->
                                                            </Border.Background>

                                                            <StackPanel Orientation="Vertical">
                                                                <TextBlock Margin="5" Text="{Binding Path=Name}" Style="{StaticResource MenuText}" FontSize="16" HorizontalAlignment="Center" />
                                                                <TextBlock Margin="5,0,5,5" Text="{Binding Path=Code, StringFormat=Kt. {0}}" Style="{StaticResource MenuText}" HorizontalAlignment="Center" />
                                                            </StackPanel>
                                                        </Border>

                                                        <ControlTemplate.Triggers>
                                                            <Trigger Property="IsMouseOver" Value="true">
                                                                <Setter TargetName="Bd" Property="Background">
                                                                    <Setter.Value>
                                                                        <!-- Removed for brevity -->
                                                                    </Setter.Value>
                                                                </Setter>
                                                            </Trigger>
                                                            <Trigger Property="Button.IsPressed" Value="true">
                                                                <Setter TargetName="Bd" Property="Background">
                                                                    <Setter.Value>
                                                                        <!-- Removed for brevity -->
                                                                    </Setter.Value>
                                                                </Setter>
                                                            </Trigger>
                                                        </ControlTemplate.Triggers>
                                                    </ControlTemplate>
                                                </Setter.Value>
                                            </Setter>
                                        </Style>
                                    </Button.Resources>
                                </Button>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Style.Resources>
            <Setter Property="Template" >
                <Setter.Value>
                    <ControlTemplate TargetType="Expander">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="30" />
                            </Grid.ColumnDefinitions>
                            <ToggleButton Grid.Column="1"
                                          IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,
                                          RelativeSource={RelativeSource TemplatedParent}}" />
                            <ContentPresenter Name="Content" Grid.Column="0" />
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsExpanded" Value="false">
                                <Setter TargetName="Content" Property="Visibility" Value="Collapsed" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Decided to add what I wanted to accomplish with the button click:

<Button Click="CompanyClick" />

CompanyClick being defined in the code behind.

+2  A: 

Change

<Button>

To...

<Button Command="{Binding OnClick}" />

On the class you use as an item in this ItemsControl, implement a read-only property which returns an ICommand for the button to use.

EDIT:

For this example, I made use of an implementation of ICommand called RelayCommand, which is available at http://msdn.microsoft.com/en-us/magazine/dd419663.aspx. See figure 3 of that article for the full RelayCommand class in C#. I converted it to Visual Basic for my use, that code is below. It does nothing more than automate the registration of commands with the WPF system, and provides you with a convenient constructor:

''' <summary>
''' Implements the ICommand interface
''' </summary>
''' <remarks>
''' Thanks to Josh Smith for this code: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
''' </remarks>
Public Class RelayCommand
    Implements ICommand
#Region "Fields"

    Private ReadOnly _execute As Action(Of Object)
    Private ReadOnly _canExecute As Predicate(Of Object)

#End Region ' Fields

#Region "Constructors"

    Public Sub New(ByVal execute As Action(Of Object))
        Me.New(execute, Nothing)
    End Sub

    Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))
        If execute Is Nothing Then
            Throw New ArgumentNullException("execute")
        End If

        _execute = execute
        _canExecute = canExecute
    End Sub
#End Region ' Constructors

#Region "ICommand Members"

    <DebuggerStepThrough()>
    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
        Return If(_canExecute Is Nothing, True, _canExecute(parameter))
    End Function

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
        AddHandler(ByVal value As EventHandler)
            AddHandler CommandManager.RequerySuggested, value
        End AddHandler
        RemoveHandler(ByVal value As EventHandler)
            RemoveHandler CommandManager.RequerySuggested, value
        End RemoveHandler
        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            CommandManager.InvalidateRequerySuggested()
        End RaiseEvent
    End Event

    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
        _execute(parameter)
    End Sub

#End Region ' ICommand Members
End Class

Using that class, you can then implement ICommands on your ViewModel, by exposing an ICommand as a read-only property in that class, along with a backing field to store the RelayCommand, which, don't forget, implements ICommand. Here's a truncated sample:

Public Class CompanyViewModel
    Implements INotifyPropertyChanged

    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

    Private _OnClick As RelayCommand
    Public ReadOnly Property OnClick As ICommand
        Get
            If _OnClick Is Nothing Then
                _OnClick = New RelayCommand(Sub()
                                                Me.OnClickExecute()
                                            End Sub,
                                            Function()
                                                Return Me.OnClickCanExecute()
                                            End Function)
            End If
            Return _OnClick
        End Get
    End Property
    Private Function OnClickCanExecute() As Boolean
        ' put a test here to tell the system whether conditions are right to execute your command.
        ' OR, just return True and it will always execute the command.
    End Function

    Private Sub OnClickExecute()
        ' put the processing for your command here; THIS IS YOUR EVENT HANDLER
    End Sub
    ' .... implement the rest of your ViewModel
End Class

The "OnClick" name is not required; the commands can take any name, because the system is not convention-based the way that VB6 was with its event handlers.

There is more than one way to do this. I'm intrigued by the "Caliburn.Micro" implementation of ICommand, which is convention-based and might make things more readable, depending on your style. But, Caliburn is an open-sourced effort by an enthusiast, albeit a very competent and qualified enthusiast. Google or Bing "Caliburn.Micro" for more information on that.

Rob Perkins
Well I'm doing a simple event handling that happens inside the class containing the itemscontrol. Should I still use this ICommand thing?Could you give me a little hint as how to use it to let the button push activate a method ( event handler ) inside the containing class? Also should I name the method OnClick for this to work?
Ingó Vals
I'll work up a more complete example and edit my answer.
Rob Perkins
Well if I understand this correctly my event handling is in the Company class. It must be inside the class that's containing the itesmControl because it's relevant to other objects there.I find it strange that I could define the button with a simple command like Click=CompanyClick but if I go into it's ControlTEmplate I can't do that anymore.
Ingó Vals
`ControlTemplate` s don't have a code-behind, so there's no way to get `Click="CompanyClick"` to be coherent. If you think about it, that's probably by design; the construct is a template, and there's no way for WPF to know whether or where it will get applied in any visual tree.
Rob Perkins
+1  A: 

There's also this:

<ControlTemplate.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
        <BeginStoryboard>
            <Storyboard>
<!-- Animations manipulating the button here -->
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
<!-- The rest of your triggers here -->
</ControlTemplate.Triggers>

This kind of mechanism in your template will give you control over the properties of the button, and possibly properties in other parts of the visual tree, depending on where you put the definiton.

You might also consider architecting things a bit differently. I wouldn't necessarily pile all the definitions into the style in quite the same way.

Rob Perkins
I actually tried using EventSetter but it didn't compile for some reason. Then I tried setting a EventSetter in a Button style and basing the other style on that and that worked fine.
Ingó Vals