开发者

WPF: Using a button as a DataTemplate, how to add click event

开发者 https://www.devze.com 2023-01-08 10:35 出处:网络
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.

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>
                            &开发者_如何学Pythonlt;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.


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号