Search code examples
c#wpfmvvmdata-bindingeventtrigger

CheckBox's in DataTemplate ListBox Binding and Triggers using MVVM


I'm learning how to use MVVM and how bind data inside a WPF App. I've created a custom CheckedListBox in XAML file this way:

        <ListBox x:Name="materialsListBox" ItemsSource="{Binding CustomCheckBox}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding Path=IsChecked, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" Content="{Binding Item}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

and also I want a single Image to dynamically show up for each CheckBox I check. I understand that I need to use Binding and UpdateSourceTrigger Property but I'm not sure how to realize this. What should I add here so that my app does what I want?

        <Image HorizontalAlignment="Left" Height="100" Margin="432,146,0,0" VerticalAlignment="Top" Width="100"/>

Here's a part of my C# code for the ViewModel:

    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<CheckedListItem<string>> _customCheckBox;
        public ObservableCollection<CheckedListItem<string>> CustomCheckBox
        {
            set
            {
                _customCheckBox = value;
                OnPropertyChanged();
            }
            get { return _customCheckBox; }
        }

        public class CheckedListItem<T> : ViewModelBase
        {
            private bool _isChecked;
            private T _item;

            public CheckedListItem()
            {
            }

            public CheckedListItem(T item, bool isChecked = false)
            {
                item = _item;
                isChecked = _isChecked;
            }

            public T Item
            {
                set
                {
                    _item = value;
                    OnPropertyChanged();
                }
                get { return _item; }
            }


            public bool IsChecked
            {
                set
                {
                    _isChecked = value;
                    OnPropertyChanged();
                }
                get { return _isChecked; }
            }
        }
...

Thank you for any recommendation.


Solution

  • One eazy way to do ProperyChanged events is to use the base set for ViewModelBase this.Set because it will raise the changed event for you.

    to do this I split up the view model and view in to 2, one for the main view and one for a view combining the check box and image. You can do it with one like you have but it was just easier for me.

    View Model for the CheckBox and image

    public class CheckBoxViewModel : ViewModelBase
    {
        private bool isChecked;
        private string imageSource;
        private string imageName;
    
        public CheckBoxViewModel(string imageSource, string imageName)
        {
            this.ImageSource = imageSource;
            this.ImageName = imageName;
    
        }
        public ICommand Checked => new RelayCommand<string>(this.OnChecked);
    
        private void OnChecked(object imageName)
        {
    
        }
        public string ImageSource
        {
            get { return this.imageSource; }
            set { this.Set(() => this.ImageSource, ref this.imageSource, value); }
        }
        public string ImageName
        {
            get { return this.imageName; }
            set { this.Set(() => this.ImageName, ref this.imageName, value); }
        }
    
        public bool IsChecked
        {
            get { return this.isChecked; }
            set { this.Set(() => this.IsChecked, ref this.isChecked, value); }
        }
    }
    

    Main Window View Model

    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<CheckBoxViewModel> items = new ObservableCollection<CheckBoxViewModel>();
    
        public ObservableCollection<CheckBoxViewModel> Items => this.items;
        public MainViewModel()
        {
            var view = new CheckBoxViewModel("Image.Jpg", "Image 1");
            this.Items.Add(view);
            var view2 = new CheckBoxViewModel("Image2.Jpg", "Image 2");
            this.Items.Add(view2);
        }
    }
    

    Checkbox and image view

    <UserControl.Resources>
        <local:MainViewModel x:Key="MainViewModel" />
        <local:MainViewModel x:Key="ViewModel" />
        <local:BoolToVisibility x:Key="BoolToVisibility" />
    </UserControl.Resources>
    <Grid >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20*"/>
            <ColumnDefinition Width="201*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <CheckBox Command="{Binding Checked}" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Path=IsChecked, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" Content="{Binding ImageName}" />
        <Image Grid.Column="1" Source="{Binding ImageSource}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding IsChecked, Converter={StaticResource BoolToVisibility}}" />
    </Grid>
    

    Main View

        <Window.Resources>
        <local:MainViewModel x:Key="MainViewModel" />
        <DataTemplate DataType="{x:Type local:CheckBoxViewModel}">
            <local:view/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ListView DataContext="{StaticResource MainViewModel}" ItemsSource="{Binding Items}"/>
    </Grid>
    

    This way the main view model adds CheckBoxViewModels to its items and then the main view automatically adds the child view to the list view.

    Whats notable is how the images visibility is flipped. I used a value converter that you add to the Images visibility Binding. It will convert a true false value to a type of Visibility.

     public class BoolToVisibility : IValueConverter
    {
        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null)
            {
                if ((bool)value)
                {
                    return Visibility.Visible;
                }
                else
                {
                    return Visibility.Collapsed;
                }
            }
    
            return Visibility.Collapsed;
        }
    
        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value that is produced by the binding target.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }