Property binding not working in ListBox WPF

2019-09-08 14:28发布

问题:

I am trying to fill a ListBox with items consisting of a grid which holds a checkbox and a textblock. I originally had only a checkbox but I need the text to wrap so I changed it to a grid with a checkbox and a textblock, with the text block doing the text wrapping.

When it was just the the checkbox, the binding worked, but now it does not. Now the correct number of items show up in the listbox but it is just an unchecked checkbox (even if I set it to checked in the code behind, see below) and a blank textblock.

XAML:

<ListBox 
    x:Name="lstCalibration" 
    Margin="304,95,78,24" 
    Background="#7FFFFFFF" 
    Width="211" 
    HorizontalAlignment="Center" 
    VerticalContentAlignment="Stretch" 
    ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
    HorizontalContentAlignment="Stretch">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Width="auto">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <CheckBox 
                    Content="" 
                    Tag="{Binding Tag}" 
                    IsChecked="{Binding IsChecked}" 
                    Checked="CheckBoxCal_Checked" 
                    Unchecked="CheckBoxCal_Unchecked"
                    Grid.Row="0" Grid.Column="0"
                    />
                <TextBlock 
                    Tag="{Binding Tag}" 
                    Text="{Binding Text}" 
                    TextWrapping="Wrap"
                    Grid.Row="0" 
                    Grid.Column="1"
                    />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Code behind:

foreach (Controller item in calList)
{
    //fill listbox with cals and correct checked states
    Grid grid = new Grid();
    CheckBox cb = new CheckBox();
    TextBlock tb = new TextBlock();
    tb.Text = item.Description;
    cb.Tag = tb.Tag = i;
    cb.IsChecked = calChecked[i];
    if (cb.IsChecked == true)
        noneChecked = false;
    i++;

    Grid.SetRow(cb, 0);
    Grid.SetColumn(cb, 0);
    grid.Children.Add(cb);
    Grid.SetRow(tb, 0);
    Grid.SetColumn(tb, 1);
    grid.Children.Add(tb);
    lstCalibration.Items.Add(grid);
}

回答1:

Add this, and delete that foreach loop where you programatically create all the stuff you already set up in your DataTemplate. It's a perfectly good DataTemplate, but your code isn't using it right. Get rid of calChecked; _items[n].IsChecked will now do that job.

At any time you can add a new ListItem to _items and it will appear in the list, or remove an existing one and it will vanish from the list. That's the "observable" part. Thanks to the PropertyChanged event raising, if you set IsChecked on any of these from code behind, the corresponding checkbox in the UI will automagically update.

public class ListItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public String Text { get; set; }

    private bool _isChecked = false;
    public bool IsChecked {
        get { return _isChecked; }
        set {
            _isChecked = value;
            PropertyChanged?.Invoke(this, 
                new PropertyChangedEventArgs(nameof(IsChecked));
        }
    }
}

In your constructor in code behind:

private ObservableCollection<ListItem> _items;
public MyWindow()
{
    InitializeComponent()

    _items = new ObservableCollection<ListItem>(
        calList.Select(
            cal => new ListItem {
                       Text = cal.Description,
                       IsChecked = cal.WhateverElse
                   }
        ));

    lstCalibration.Items = _items;
}

One small change to DataTemplate in two places: Don't waste time binding Tag. In the event handlers (which you may not even need), just cast like so:

public void CheckBoxCal_Checked(object sender, EventArgs args)
{
    var listItem = ((FrameworkElement)sender).DataContext as ListItem;

    //  Do whatever. listItem.IsChecked will be up to date thanks 
    //  to the binding.
}

XAML:

            <CheckBox 
                Content="" 
                IsChecked="{Binding IsChecked}" 
                Checked="CheckBoxCal_Checked" 
                Unchecked="CheckBoxCal_Unchecked"
                Grid.Row="0" Grid.Column="0"
                />
            <TextBlock 
                Text="{Binding Text}" 
                TextWrapping="Wrap"
                Grid.Row="0" 
                Grid.Column="1"
                />