3

I have a situation with a ListBox control. Inside the item template there is a button. When I click the button I want to selected item of said ListBox to change to the item the button is in. At the moment, I can change the selected item by clicking elsewhere within the item template, but that doesn't work if I select the button. To clarify that last sentence for a commenter, if one clicks in the item template that is not the button, the SelectedItem will change as expected. If you click on the button within the item template, the SelectedItem will not change.

Further information: I am using MVVM and the button has attached to it a command within a view model. Any solution would need to allow this to continue to work.

The ListBox is linked to ItemSource, and the SelectedItem of the ListBox is bound to a property in the view model.

If there's a set way to do this, I've so far been unable to find it.

I am using C# and Visual Studio 2010.

Thanks.

  • 1
    A bit of xaml and cs would be really helpful. *"that doesn't work if I select the button"* - how exactly it doesn't work? – Sinatr Aug 02 '17 at 13:22

4 Answers4

3

If you can use ToggleButton and Bind the IsChecked to the ListBoxItem IsSelected property then it will be fine.
Reason because your ListBoxItem is not selected is because the Button is handling MouseDown event, thus making the ListBoxItem unaware of the click. In your button create an event handler for Click and set e.Handled = false;.

XAMlMAX
  • 2,268
  • 1
  • 14
  • 23
  • I'm sorry, I don't understand. Are these two things you say both necessary? – TheFaithfulLearner Aug 02 '17 at 14:35
  • @TheFaithfulLearner No worries, what I meant is _If you can_, that means if you don't have to use a `Button`. That would imply if you need the button to be `un clicked` once done. But no both things are not necessary. Try to add Click event handler and just set `e.handled = false;` – XAMlMAX Aug 02 '17 at 14:45
  • @TheFaithfulLearner And one more thing. When you put the event handler make sure in XAML it is before the Command Biding so it will be executed before the command. – XAMlMAX Aug 02 '17 at 14:57
  • 1
    It seems you speak the truth. I played around with it a bit, but yes, it seems it is this simple. I love a nice, simple solution! Thanks! – TheFaithfulLearner Aug 02 '17 at 20:53
  • 1
    For some reason that worked then and doesn't work now. I'm at a loss to explain it. However, I can set the list box's selected item in that click handler method and set the handled to false, which then does both jobs. – TheFaithfulLearner Aug 03 '17 at 10:33
  • @TheFaithfulLearner Have you changed anything since then? Like `DataTemplate` or anything else? Cause it shouldn't do that! LOL – XAMlMAX Aug 03 '17 at 11:43
2

A bit of your code would have been helpful, but here's a dummy example of how to do select a ListBoxItem using a click on a button, through the MVVM pattern.

public class MyViewModel : BaseViewModel // implements INotifyPropertyChanged
{
    private ICommand _myCommand;

    public ICommand MyCommand { get {return _myCommand;} private set { _myCommand = value; OnPropertyChanged(); }}

    private ObservableCollection<int> _myObjects;

    public ObservableCollection<int> MyObjects { get {return _myObjects;} private set {_myObjects = value; OnPropertyChanged();}}

    private int _mySelectedObject;

    public int MySelectedObject { get {return _mySelectedObject;} set {_mySelectedObject = value; OnPropertyChanged(); }}

    public MyViewModel 
    {
        MyCommand = new RelayCommand(SetSelectedObject); // the source code for RelayCommand may be found online.
    }

    private void SetSelectedObject(object obj) 
    {
        int myInt = (int)obj;

        MySelectedObject = myInt;
    }
}

Some XAML parts have been erased in order to keep it simple. Do not simply copy/paste this snippet, adapt it to your code.

<UserControl x:Name="root" DataContext="{Binding MyViewModel, Source={StaticResource Locator.MyViewModel}}">
    <ListBox ItemsSource="{Binding MyObjects}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding }"/>
                    <Button Command="{Binding MyCommand, ElementName=root}" CommandParameter="{Binding }"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</UserControl>

I have not tested this code. So there might be some mistakes. Don't hesitate to point these out, and I will update my code.

EDIT : Here is the source code for my RelayCommand implementation (modified from Telerik):

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

    private Action<object> _methodToExecute;
    private Func<object, bool> _canExecuteEvaluator;

    public RelayCommand(Action<object> methodToExecute, Func<object, bool> canExecuteEvaluator)
    {
        _methodToExecute = methodToExecute;
        _canExecuteEvaluator = canExecuteEvaluator;
    }

    public RelayCommand(Action<object> methodToExecute)
        : this(methodToExecute, null)
    {
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecuteEvaluator == null)
        {
            return true;
        }
        else
        {
            bool result = _canExecuteEvaluator.Invoke(parameter);
            return result;
        }
    }

    public void Execute(object parameter)
    {
        _umethodToExecute.Invoke(parameter);
    }
}
Atlasmaybe
  • 1,511
  • 11
  • 26
  • Hi there. Thanks for the information. I started putting this into practise, but when I tried to initialise MyCommand to be a new RelayCommand with the parameter 'SetSelectedObject'. That method exists too, and is as you have written it. The error given is 'Argument 1: cannot convert from 'method group' to 'System.Action'. I do use MVVM Light... maybe that has an effect on things. – TheFaithfulLearner Aug 02 '17 at 14:10
  • If you want to use my solution, you might need to use another RelayCommand implementation. Please refer to my post, I just edited it. – Atlasmaybe Aug 02 '17 at 14:17
1

You can get the Listbox that is parent of the button using this function:

    Function GetParent(child As UIElement, parentType As Type) As UIElement
        If child Is Nothing Then Return Nothing
        Dim p = child
        Do
            p = TryCast(VisualTreeHelper.GetParent(p), UIElement)
            If p Is Nothing Then Return Nothing
            If p.GetType Is parentType Then Return p
        Loop
    End Function

This is the code that selects the item. Add it to the Click event of the button:

Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
  Dim lst As ListBox = GetParent(sender, GetType(ListBox))
  lst.SelectedIndex = lst.Items.IndexOf(Me.DataContext)
End Sub
Eng. M.Hamdy
  • 306
  • 1
  • 3
  • 12
0

C# version:

  private void Button_Click(object sender, RoutedEventArgs e)
    {
        Button b = sender as Button;
        TListBoxItems data = b.DataContext as TListBoxItems;
        ListBox aLB = new ListBox();
        aLB = (ListBox)GetParent(sender, aLB.GetType());
        aLB.SelectedIndex = aLB.Items.IndexOf(data);
    }

    public object GetParent(object child, Type parentType)
    {
        if (child == null)
            return null/* TODO Change to default(_) if this is not a reference type */;
        var p = child;
        do
        {
            p = VisualTreeHelper.GetParent((UIElement)p) as UIElement;
            if (p == null)
                return null/* TODO Change to default(_) if this is not a reference type */;
            if (p.GetType() == parentType)
                return p;
        }
        while (true);
    }