views:

761

answers:

4

I am trying to detect when an item is checked, and which item is checked in a ListBox using Silverlight 4 and the Prism framework. I found this example on creating behaviors, and tried to follow it but nothing is happening in the debugger. I have three questions:

  1. Why isn't my command executing?
  2. How do I determine which item was checked (i.e. pass a command parameter)?
  3. How do I debug this? (i.e. where can I put break points to begin stepping into this)

Here is my code:

View:

        <ListBox x:Name="MyListBox" ItemsSource="{Binding PanelItems, Mode=TwoWay}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding Enabled}" my:Checked.Command="{Binding Check}"  />
                        <TextBlock x:Name="DisplayName" Text="{Binding DisplayName}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

ViewModel:

public MainPageViewModel()
{
    _panelItems.Add( new PanelItem
    {
        Enabled = true,
        DisplayName = "Test1"
    } );

    Check = new DelegateCommand<object>( itemChecked );
}

public void itemChecked( object o )
{
//do some stuff
}

public DelegateCommand<object> Check { get; set; }

Behavior Class

public class CheckedBehavior : CommandBehaviorBase<CheckBox>
    {
        public CheckedBehavior( CheckBox element )
            : base( element )
        {
            element.Checked +=new RoutedEventHandler(element_Checked);
        }

        void element_Checked( object sender, RoutedEventArgs e )
        {
            base.ExecuteCommand();
        }               
    }

Command Class

public static class Checked
{
    public static ICommand GetCommand( DependencyObject obj )
    {
        return (ICommand) obj.GetValue( CommandProperty );
    }

    public static void SetCommand( DependencyObject obj, ICommand value )
    {
        obj.SetValue( CommandProperty, value );
    }

    public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached( "Command", typeof( CheckBox ), typeof( Checked ), new
            PropertyMetadata( OnSetCommandCallback ) );

    public static readonly DependencyProperty CheckedCommandBehaviorProperty =
                DependencyProperty.RegisterAttached( "CheckedCommandBehavior", typeof( CheckedBehavior ), typeof( Checked ), null );

    private static void OnSetCommandCallback( DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e )
    {
        CheckBox element = dependencyObject as CheckBox;
        if( element != null )
        {
            CheckedBehavior behavior = GetOrCreateBehavior( element );
            behavior.Command = e.NewValue as ICommand;
        }
    }
    private static CheckedBehavior GetOrCreateBehavior( CheckBox element )
    {
        CheckedBehavior behavior = element.GetValue( CheckedCommandBehaviorProperty ) as CheckedBehavior;
        if( behavior == null )
        {
            behavior = new CheckedBehavior( element );
            element.SetValue( CheckedCommandBehaviorProperty, behavior );
        }

        return behavior;
    }
    public static CheckedBehavior GetCheckCommandBehavior( DependencyObject obj )
    {
        return (CheckedBehavior) obj.GetValue( CheckedCommandBehaviorProperty );
    }
    public static void SetCheckCommandBehavior( DependencyObject obj, CheckedBehavior value )
    {   
        obj.SetValue( CheckedCommandBehaviorProperty, value );
    }               

}

+2  A: 

Your sample is not enough for a repro on my PC, but here are the things that I'd correct first:

  • The bindings in the DataTemplate are missing ", Mode=TwoWay" if you want the Enabled property to be set in your PanelItem (- The ItemsSource binding does not need the Mode=TwoWay, but this is a minor detail)
  • The DataContext of the ItemTemplate is the PanelItem instance, so the binding of the Check command seems wrong: there is no Check property on PanelItem. The binding should be: my:Checked.Command="{Binding ElementName=MyListBox, Path=DataContext.Check}

This kind of stuff is always hard to debug. Look at the output window of VS; binding errors (path not found) are displayed there. When you have a DP change callback (like OnSetCommandCallback), a breakpoint there will tell you how the binding went.

Edit: added after 1st comment (as I can't use the comment feature on the PC I have to use now) The Command attached property is defined as type CheckBox in the Checked class, but the Check property in the VM is a DelegateCommand. I agree with WPF on the type mismatch :-) The property declaration is like this:

public static readonly DependencyProperty CommandProperty = 
    DependencyProperty.RegisterAttached( 
        "Command", typeof( CheckBox ), 
        typeof( Checked ), new PropertyMetadata( OnSetCommandCallback ) ); 

The second parameter should be the property type, so I guess something like ICommand in your case.

Out of curiosity: in OnSetCommandCallback, you don't care for the value set to the Command property (which is in e.NewValue). How do you relate an instance of CheckedBehavior to the Check property of the VM ?

Edit after second comment: No, the 2nd paragraph above is not related to your question. Maybe it does not make sense. I can't figure out the role of CheckedBehavior.

Concerning the question of which item is checked/unchecked: what do you need more precisely ? You have a PanelItem instance, whose Enabled property is being set to true or false through the biding; so the checked items are the ones with Enabled=true.

Edit after 3rd comment: Thanks for the explanation of your needs. You're not really using the path parameter of the binding to the attached property, you could write:

my:Checked.Command="{Binding}"

This way, e.NewValue is the bound PanelItem in the OnSetCommandCallback. So it could be given to the CheckedBehavior instance (in its constructor), which could forward it when calling Execute of ICommand.

Timores
@Timores - What you said about the DataContext for PanelItem not being the same as the ListBox makes sense. I made the change like you suggested and now I am getting this error: "Can't convert type Microsoft.Practices.Composite.Presentation.Commands.DelegateCommand`1[System.Object] to type System.Windows.Controls.CheckBox." If you need more code to help troubleshoot, please let me know and I'll try to get it to you.
Blake Blackwell
Ok, first the good news. The first comment you made on the 2nd parameter corrected the conversion type issue I was having. Now I am able to see the item be checked/unchecked in my VM! I'm not quite sure I understand your second comment. Is this why I can't determine which item has been checked/unchecked? How do I go about figuring which list item has actually been acted upon? I hope this makes sense and I really appreciate your help thus far!
Blake Blackwell
@Timores - Concerning edit two: If I could get the DisplayName property as a command parameter that would be ideal. In a real world example I would probably have an ID that I would want to pass back and forth.
Blake Blackwell
Ok, I got it working - much thanks to you. I appreciate your patience and help!
Blake Blackwell
You're welcome. I like the main principle of SO, i.e. helping other people.
Timores
A: 

I'm stuck with the same problem as you. I have reproduced checked/unchecked events, thought i still don't understand how to pass parameter. Could you, please, explain this to me?

Walkor
Sorry, i found the way. It's not a question anymore. Just created 1 more property and setter in the Checked class.
Walkor
0 Can you post your solution? I'm having trouble getting the command to execute, also I'm in the need of passing a parameter... Would be much appreciated!
Mcad001
A: 

Walkor/Blake, any chance that you guys can post your solutions? I'm having trouble getting the command to execute, also I'm in the need of passing a parameter... Would be muc appreciated!

Mcad001
A: 

CheckBehavior.cs:

public class CheckBehavior : CommandBehaviorBase<CheckBox>
{
    public CheckBehavior(CheckBox element) : base(element)
    {
        element.Checked += OnElementChecked;
        element.Unchecked += OnElementChecked;
    }

    private void OnElementChecked(object sender, RoutedEventArgs e)
    {
        if (sender is CheckBox && ((CheckBox)sender).IsPressed)
        {
            base.ExecuteCommand();
        }
    }
}

CheckCommand.cs:

public class CheckCommand
{
    public static ICommand GetCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    public static void SetCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(CommandProperty, value);
    }

    public static object GetCommandParameter(DependencyObject obj)
    {
        return obj.GetValue(CommandParameterProperty);
    }

    public static void SetCommandParameter(DependencyObject obj, object value)
    {
        obj.SetValue(CommandParameterProperty, value);
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CheckCommand), new PropertyMetadata(OnSetCommandCallback));

    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CheckCommand), new PropertyMetadata(OnSetCommandParameterCallback));

    public static readonly DependencyProperty CheckedCommandBehaviorProperty =
        DependencyProperty.RegisterAttached("CheckedCommandBehavior", typeof(CheckBehavior), typeof(CheckCommand), null);

    private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        CheckBox element = dependencyObject as CheckBox;
        if (element != null)
        {
            CheckBehavior behavior = GetOrCreateBehavior(element);
            behavior.Command = e.NewValue as ICommand;
        }
    }

    private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        CheckBox element = dependencyObject as CheckBox;
        if (element != null)
        {
            CheckBehavior behavior = GetOrCreateBehavior(element);
            behavior.CommandParameter = e.NewValue;
        }
    }

    private static CheckBehavior GetOrCreateBehavior(CheckBox element)
    {
        CheckBehavior behavior = element.GetValue(CheckedCommandBehaviorProperty) as CheckBehavior;
        if (behavior == null)
        {
            behavior = new CheckBehavior(element);
            element.SetValue(CheckedCommandBehaviorProperty, behavior);
        }

        return behavior;
    }
    public static CheckBehavior GetCheckCommandBehavior(DependencyObject obj)
    {
        return (CheckBehavior)obj.GetValue(CheckedCommandBehaviorProperty);
    }
    public static void SetCheckCommandBehavior(DependencyObject obj, CheckBehavior value)
    {
        obj.SetValue(CheckedCommandBehaviorProperty, value);
    } 
}

And example:

<CheckBox Commands:CheckCommand.Command="{Binding MyCheckCommand}}"
          Commands:CheckCommand.CommandParameter="{Binding}"/>
Walkor