Search code examples
c#wpfcheckboxdata-bindinglistbox

How to show the checked items in my CheckBox


EDITED:

I've created three ListBoxes:

1st ListBox: listBox, which shows a list of U.S. States

2nd ListBox: listBox_Mirror, which contains a CheckBox to show the selected items of "listBox"

3rd ListBox: (No Name), which is supposed to show the checked items of "listBox_Mirror"

1st ListBox -> 2nd ListBox works fine. However, 2nd ListBox -> 3rd ListBox doesn't work, as you can see in the picture below:

enter image description here

Procedure:

  1. Select all four items in the 1st ListBox.

  2. Check the first two items (California and Illioni) in the 2nd ListBox's CheckBox.

  3. See if California and Illioni are shown in the 3rd ListBox.

(In my case, nothing is shown.)

Here are my codes:

StateList.cs

using System.Collections.ObjectModel;

namespace CheckedListBox
{
    public class StateList
    {
        public ObservableCollection<string> Data { get; }
        public StateList()
        {
            Data = new ObservableCollection<string>();
            Data.Add("California");
            Data.Add("Illinoi");
            Data.Add("Michigan");
            Data.Add("New York");
        }
    }
}

MainWindow.xaml

<Window x:Class="CheckedListBox.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:CheckedListBox"
    mc:Ignorable="d"
    Title="MainWindow" Height="500" Width="400">
<Grid>
    <ListBox ItemsSource="{Binding Path=Data}" x:Name="listBox" Margin="100,50,100,300" SelectionMode="Multiple"/>
    <ListBox ItemsSource="{Binding SelectedItems, ElementName=listBox}" x:Name="listBox_Mirror" Margin="100,200,100,150">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListBoxItem">
                            <CheckBox Content="{TemplateBinding Content}"/>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
    <ListBox ItemsSource="{Binding SelectedItems, ElementName=listBox_Mirror}" Margin="100,350,100,25"/>
</Grid>

MainWindow.xaml.cs

using System.Windows;

    namespace CheckedListBox
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new StateList();
            }
        }
    }

... I've changed the Binding property of the 3rd ListBox based on the IntelliSense's suggestion, but it doesn't work, too. Please tell me how to fix this. Thank you.


Solution

  • Two things, there is no binding to the IsChecked and hence nothing ever is set. Also your data is just reference of strings; you should change it to a class with at least two properties, one a Boolean and the other the string you have now. Then bind appropriately.


    Here is how to do it. I have a model which is defined in code behind but you can get the idea on its structure.

      <Page ...
         xmlns:model="clr-namespace:WPFStack.Model"/>
     ...
    <Page.Resources>
        <model:Orders x:Key="Orders">
            <model:Order CustomerName="Alpha"
                    OrderId="997"
                    InProgress="True" />
            <model:Order CustomerName="Beta"
                    OrderId="998"
                    InProgress="False" />
            <model:Order CustomerName="Omega"
                    OrderId="999"
                    InProgress="True" />
            <model:Order CustomerName="Zeta"
                    OrderId="1000"
                    InProgress="False" />
        </model:Orders>
    

    Now with my Listbox

    <ListBox ItemsSource="{StaticResource Orders}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel HorizontalAlignment="Stretch" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding CustomerName}"
                          IsChecked="{Binding InProgress, Mode=TwoWay}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    

    Running it shows this:

    enter image description here

    Model

    namespace WPFStack.Model
    {    
        /// <summary>Class Orders which is a placeholder for Xaml example data.</summary>
        public class Orders : List<Order> { }
        public class Order
        {
            public string CustomerName { get; set; }
            public int OrderId { get; set; }
            public bool InProgress { get; set; }
        }
    }
    

    Mirror

    Ok, now I will name the controls lbOriginal and lbSelected to be accessible in the code behind. The new control lbSelected will mirror as such without directly connecting to the lbOriginal control or the data:

    <ListBox x:Name="lbShowSelected">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel HorizontalAlignment="Stretch" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding .}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    

    Then I would subscribe to events such as Loaded, Checked and UnChecked on the original.

    <ListBox x:Name="lbOriginal"
             ItemsSource="{StaticResource Orders}"
             Loaded="ProcessChange">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel HorizontalAlignment="Stretch" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding CustomerName}"
                            IsChecked="{Binding InProgress, Mode=TwoWay}"
                            Checked="ProcessChange"
                            Unchecked="ProcessChange"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    

    So then at each step ProcessChange method properly updates the mirror (selected as I call it):

    private void ProcessChange(object sender, RoutedEventArgs e)
    {
        if (lbOriginal.ItemsSource is Orders asOrders)
        {
            lbShowSelected.ItemsSource = null; // Inform control of reset
            lbShowSelected.ItemsSource = asOrders.Where(ord => ord.InProgress)
                                                 .Select(ord => ord.CustomerName)
                                                 .ToList();
        }
    }
    

    Then it is in sync and mirroring

    enter image description here