3

See code below
Visual Studio 2010
Above the ListBox have a TextBox.
Via binding the TextBox can get bigger or smaller when an item is selected.
That causes the ListBox to move.
When the ListBox moves the selected item is NOT the item that was clicked.
The selected item is the item under the mouse on the moved ListBox.
Some times it will not even select at all (try and go from 9 to 10 or from 10 to 9).
In this code to reproduce the problem even and odd produce different lengths.
So if you go from odd to odd or even to even then no problem.
If you go from on odd at the top an even at the bottom (without scrolling) then sometimes an item that was not even in view will be selected.
In the real code the TextBox is a description of the item and the descriptions are different lengths.
Interesting is in debug and there is a break point on get { return boundText; } then it does select the proper item.
I think it processes the select, then measures out the UI, and then processes the select a second time on the new UI.
Since it behaves different in debug it is hard to figure out.

<Window x:Class="ListBoxMissClick.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Path=BoundText}" TextWrapping="Wrap" />
        <ListBox Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Path=BoundList}" SelectedItem="{Binding Path=BoundListSelected, Mode=TwoWay}"/>
    </Grid>
</Window>

using System.ComponentModel;

namespace ListBoxMissClick
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string boundListSelected;
        private string boundText = string.Empty;
        private List<string> boundList = new List<string>();
        private bool shortLong = true;
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        public MainWindow()
        {
            for (int i = 0; i < 1000; i++)
            {
                boundList.Add(i.ToString());
            }

            InitializeComponent();

        }
        public string BoundText 
        { 
            get { return boundText; }
            set 
            { 
                if (boundText != value)
                {
                    boundText = value;
                    NotifyPropertyChanged("BoundText");
                }
            }
        }
        public List<string> BoundList { get { return boundList; } }
        public string BoundListSelected
        {
            get { return boundListSelected; }
            set
            {
                boundListSelected = value;
                if (Int32.Parse(value) % 2 == 0)
                {
                    BoundText = value.ToString() + " something very long something very long something very long something very long something very long something very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very long";
                }
                else
                {
                    BoundText = value.ToString() + " something short "; 
                }
             }
        }

        private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            BoundText = " something very long something very long something very long something very long something very long something very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very long";
        }
    }
}

In addition to the accepted answer Mouse.Capture and ReleaseMouseCapture work.

set
{
    Mouse.Capture(this);
    {
        boundListSelected = value;
        if (Int32.Parse(value) % 2 == 0)
        {
            BoundText = value.ToString() + " something very long something very long something very long something very long something very long something very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very long";
        }
        else
        {
            BoundText = value.ToString() + " something short ";
        }
    }
    ReleaseMouseCapture();
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176
  • hehe, yeah it's really fun if you just hold the mouse down. So, the layout is updated and the ListBoxItem that's under the mouse now says "yay! mouse is down I'm selected!" The only reason it works when there's a break point is that it gives you the opportunity to get the mouse button up before the cycle continues. Unfortunately, I don't know how to fix it - I tried trapping the mouse events and setting a flag to force further selections to stop until the mouse goes up, but I couldn't get it to work. Good luck! – tordal Feb 07 '13 at 18:39

2 Answers2

4

I've rewritten your code a bit. The trick is to use a MouseCapture to avoid having multiple event handling (with your original code, the listBox was getting up to three selection for a single click due to the layout changeing while mouse button was pressed)

Here is the code :

MainWindow.xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="TextEditor.MainWindow"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Text="{Binding Path=BoundText}" TextWrapping="Wrap" />
        <ListBox Grid.Row="1" 
                 ItemsSource="{Binding Path=BoundList}" 
                 SelectedItem="{Binding Path=BoundListSelected, Mode=TwoWay}"/>
    </Grid>

</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

namespace TextEditor
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public string BoundText
        {
            get { return (string)GetValue(BoundTextProperty); }
            set { SetValue(BoundTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BoundText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BoundTextProperty =
            DependencyProperty.Register("BoundText", typeof(string), typeof(MainWindow), new PropertyMetadata(string.Empty));

        public string BoundListSelected
        {
            get { return (string)GetValue(BoundListSelectedProperty); }
            set { SetValue(BoundListSelectedProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BoundListSelected.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BoundListSelectedProperty =
            DependencyProperty.Register("BoundListSelected", typeof(string), typeof(MainWindow), new PropertyMetadata(string.Empty, OnBoundListSelectedChanged));

        private static void OnBoundListSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var mainWindow = d as MainWindow;
            var value = e.NewValue as string;

            Mouse.Capture(mainWindow);

            if (Int32.Parse(value) % 2 == 0)
            {
                mainWindow.BoundText = value.ToString() + " something very long something very long something very long something very long something very long something very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very long";
            }
            else
            {
                mainWindow.BoundText = value.ToString() + " something short ";
            }

            mainWindow.ReleaseMouseCapture();
        }

        public MainWindow()
        {
            for (int i = 0; i < 1000; i++)
            {
                boundList.Add(i.ToString());
            }

            InitializeComponent();
            DataContext = this;
        }

        public List<string> BoundList { get { return boundList; } }
        private List<string> boundList = new List<string>();
    }
}

Edit : I actually changed the way MainWindow was coded (it's not necessary to implement INotifyPropertyChanged on a DependencyObject, so i just removed it and set two dependency properties) but you could try solving your issue with your ogirinal code by simply capturing the mouse before setting BoundText, and then releasing it.

Sisyphe
  • 4,626
  • 1
  • 25
  • 39
  • +1 Worked. Will give you a check if nothing shorter comes up in the next hour. I was able to capture the mouse and that does fix the first selection. But I cannot figure out how to release the mouse. – paparazzo Feb 07 '13 at 19:28
  • Glad I could help. Releasing the mouse is done by calling the ReleaseMouseCapture() method on the UIElement that previously captured it :) – Sisyphe Feb 08 '13 at 08:32
  • Mouse.Capture and ReleaseMouseCapture worked and is the solution I went with as less code changes and I am not familiar with DependencyProperty. Do you see any risk in that solution. Thanks again. – paparazzo Feb 08 '13 at 13:56
  • No risk at all. MouseCapture only redirects all mouse events on a specific UIElement until you releases it. – Sisyphe Feb 08 '13 at 15:34
  • I know it has been a long time but I have situation where where the collection and class come from separate class file. The set does not have access to the mouse. Do you know how I can make that situation work? – paparazzo Jun 07 '13 at 15:44
1

You can add Thread.Sleep in BoundListSelected setter to solve your problem, but I think that better solution will be using grid columns in this situation. When you use columns you need not to use Thread.Sleep.

public string BoundListSelected
{
    get { return boundListSelected; }
    set
    {
        Thread.Sleep(TimeSpan.FromSeconds(.2));
        boundListSelected = value;
        if (Int32.Parse(value) % 2 == 0)
        {
            BoundText = value.ToString() + " something very long something very long something very long something very long something very long something very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very longsomething very long something very long something very long something very long something very long";
        }
        else
        {
            BoundText = value.ToString() + " something short ";
        }
    }
}

If you want not use Thread.Sleep you can use grid with columns:

<Grid>
    <Grid.ColumnDefinitions>            
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Column="0" Text="{Binding Path=BoundText}" TextWrapping="Wrap" />
    <ListBox Grid.Column="1" ItemsSource="{Binding Path=BoundList}" SelectedItem="{Binding Path=BoundListSelected, Mode=TwoWay}"/>
</Grid>
kmatyaszek
  • 19,016
  • 9
  • 60
  • 65