开发者

Accessing a button click for a button in a ListBox DataTemplate on a Silverlight Templated Control

I have a Silverlight Templated Control (not a user control), which contains a ListBox.

In the DataTemplate of the ListBox i have a Button, like so:

                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="*" />
                                    </Grid.ColumnDefinitions>
                                    <ProgressBar Grid.Column="0" Width="70" Height="20" Value="{Binding Path=Percentage}" Minimum="0.0" Maximum="100.0" />
                                    <TextBlock Grid.Column="0" Text="{Binding Path=Percentage, StringFormat='{}{0:##0.0}%'}" Margin="10,3,3,3" HorizontalAlignment="Center" />
                                    <TextBlock Grid.Column="1" Text="{Binding Path=File.Name}" Margin="3" />
                                    <Button Grid.Column="2" Content="Remove" x:Name="RemoveButton" Command="{TemplateBinding DeleteCommand}" Style="{TemplateBinding UploadButtonStyle}" HorizontalAlignment="Right" Margin="0,0,5,0" />
                                </Grid>
                            </DataTemplate>
                        </ListBox.ItemTemplate>

See the button there at the end of the template? HOW CAN I ACCESS IT'S CLICK EVENT? I can't use the GetTemplateChild() method since the button is part of the DataTemplate. I've tried Commanding (as you can see above). Seems like that's the way to go, although the Templated Control isn't exactly MVVM.

Any ideas? Maybe something other than Commanding? or else I'm doing the commanding wrong?

here's some relevant code:

...the Dependency Property / Property definitions... (should it be a Dep Prop?)

    public static readonly DependencyProperty DeleteCommandProperty =
        DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(MultipleFileUpload), new PropertyMetadata(null));

    public ICommand DeleteC开发者_JAVA百科ommand
    {
        get { return (ICommand)GetValue(DeleteCommandProperty); }
        set 
        { 
            SetValue(DeleteCommandProperty, value);
            FirePropertyChanged("DeleteCommand");  //INotifyPropertyChanged stuff
        }
    }

... in OnApplyTemplate()...

    public override void OnApplyTemplate()
    {
        ....
        DeleteCommand = new DelegateCommand(RemoveItemFromList, CanRemove);
        ....
        base.OnApplyTemplate();
    }

...the ICommand Action...

    private void RemoveItemFromList(object commandParameter)
    {
        //NEVER GETTING HERE!
    }

I hope it's something small.

Thanks people!

Kevin


I've added a command as a property to the class of the objects I bind into ListBoxes's (and other ItemsControl's) ItemSource. This does mean I have to change my "data" objects to handle GUI events - which often seemed wrong and hacky.

I've also derived ItemsControl (but since a listbox is an ItemsControl this may still apply). I add my own properties the derived control that I'll ultimately want to access from the items. In your case the button command handler. It should be easy to set these properties since they aren't locked-up in that nested template.

Next, I overrided GetContainerForItemOverride() in that derived class and return another class, my own derived ContentPresenter. This new ContentPresenter should also have that same command property - set it equal to ItemControl's command in GetContainerForItemOverride when you construct it.

Now in the DataTemplate use TemplateBinding (not regular Binding) to get to that Command.

I've kicked around the item of trying to make a generic/reusable version of all of this.

Edit, basic example :

class MyItemsControl : ItemsControl
{
   public Command MyCommand {get;set;} // I've often use a full-blown DP here

snip

   protected override DependencyObject GetContainerForItemOverride()
   {
       return new MyContentPresenter(this.MyCommand); // MyContentPresenter is just a derived ContentPresenter with that same property.
   }

Edit again:

I've also put code in ItemsControl.PrepareContainerForItemOverride. This method gives you both the ContentControl (your own one if you're overriding GetContainerForItemOverride) and the current "Item" in the list. In here you can also do further initialization of the ContentControl instance - if what you want to do depends on the object that it's being bound to.


I suggest you use a single relaycommand:

public class RelayCommand<T> : ICommand
{
    #region Fields

    readonly Action<T> _execute = null;
    readonly Predicate<T> _canExecute = null;

    #endregion // Fields

    #region Constructors

    public RelayCommand(Action<T> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<T> execute, Predicate<T> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute((T)parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

    #endregion // ICommand Members
}

XAML:

<Button Grid.Column="2" Content="Remove" x:Name="RemoveButton" Command="{Binding DeleteCommand}" CommandParameter={Binding} Style="{TemplateBinding UploadButtonStyle}" HorizontalAlignment="Right" Margin="0,0,5,0" />

what this will do is everytime you click on the button, it will invoke the same deletecommand, but will pass the current item as parameter.

Hope this helps


I've come across this idea in MSDN, I have not tried it but I figured it was worth sharing here:

The DataContext of the items in the list box is not the same as the views DataContext. Each item's DataContext refers to an item in the collection that is bound to the list box's ItemsSource property.

A solution is to bind the command property to a static resource and set the value of the static resource to the command you want to bind. This is illustrated in the following XAML from the Stock Trader RI.

<!--Specifying the observablecommand in the view's resources-->
<UserControl.Resources>
   <Infrastructure:ObservableCommand x:Key="BuyCommand" />
</UserControl.Resources>

<!—Binding the Button Click to the command. This control can sit inside a datagrid or a   list box. -->
<Button Commands:Click.Command="{Binding Path=Value, Source={StaticResource BuyCommand}}" Commands:Click.CommandParameter="{Binding Path=TickerSymbol}" />

Then in the code-behind of the view, you must specify that the value of the resource actually points to the command on the presentation model. The following is an example of this from the Stock Trader RI, where the BuyCommand property on the presentation model is put in the resources.

((ObservableCommand)this.Resources["BuyCommand"]).Value = value != null ? value.BuyCommand : null;


Hi you can use relative source and AncesterType. Then its works fine for me.

Refer the below code.

<Button Content="Delete"  Command="{Binding DataContext.DeleteCommand,
          RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}"
          CommandParameter="{Binding Path=SelectedItem, RelativeSource= {RelativeSource FindAncestor, AncestorType=
                 {x:Type ListBox}}}"/>
0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜