0

I am generating a ItemsControl that contains a list of CheckBoxes which are generated by a List.

The situation

I am reading the Id3 Tags of an mp3 file, especially the "comment" field. Then I am searching with a regex for all substrings between two brackets "[(.*?)]". These are the tags the file contains.

App Settings

On Startup I am reading a config file into "AppSettings" which contains a list of Tags that serve as the available tags. This list is used to generate the CheckBoxes for the UI.

public class AppSettings
{
    ...

    public List<Tag> LocationTags { get; set; } = new List<Tag>()
    {
        new Tag("Club", "CLUB"),
        new Tag("Bar", "BAR"),
        new Tag("Radio", "RADIO")
    };

    ...

enter image description here

The class Audiofile

This is a model class which stores some data, also the tags extracted from the "comments" field

public class Audiofile {
    ...

    public List<Tag> LocationTags
    {
        get => _locationTags;
        set
        {
            SetProperty(ref _locationTags, value);
            HasChanges = true;
        }
    }
}

The Tag class

public class Tag
{
    /// <summary>
    /// A User friendly name
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// The key which is tagged, e.g. charts which is then encoded into [charts]
    /// </summary>
    public string Key { get; set; }
}

The View

The View, as already described, shows the predefined tags with CheckBoxes.

<ItemsControl ItemsSource="{Binding AppSettings.LocationTags}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox Margin="16,4,4,4"
                    IsChecked="{Binding ElementName=Root, Path=DataContext.SelectedAudiofile.LocationTags.ThisIsWhatIWantToBind}" />
                                <TextBlock Text="{Binding Name}"
                                           Margin="0,4,4,4"
                                           Style="{StaticResource TextBlockLight}" />
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The ViewModel

Basically provides the view with the needed data.

public class xxViewModel{
    ...

    public Audiofile SelectedAudiofile { get;set; }
}

The Problem

As you can see, It's not a problem to generate the CheckBoxes by the List, but how can I set the IsChecked Property on them based if the Audiofile.LocationTags contains a Tag with the equal Key? Also I want to add a new Tag to the Audiofile.LocationTags List when a CheckBox.IsChecked state has changed

DirtyNative
  • 2,553
  • 2
  • 33
  • 58
  • This might be helpful for you https://stackoverflow.com/questions/15480279/wpf-check-box-check-changed-handling. You can add boolean prop to tag clas which indicates if you condition occurs, and for the second part see the link above – Michael Gabbay Jun 05 '20 at 14:23
  • What property should that be? Also before that, I need to get the correct tag – DirtyNative Jun 05 '20 at 14:32

1 Answers1

0

All you have to do is to add a corresponding property to the data model.

As the class Tag serves as a binding source, it should implement INotifyPropertyChanged. Every binding source that is not a DependencyObject and therefore cannot implement a DependencyProperty should always implement INotifyPropertyChanged.

The view model listens to model changes:

public class xxViewModel : INotifyPropertyChanged
{
    public xxViewModel()
    {
        this.SelectedAudiofile = new Audiofile();

        // Initialize each Tag item
        var tag = new Tag() { IsEnabled = true };
        tag.EnabledChanged += OnAudiofileTagChanged;

        this.SelectedAudiofile.LocationTags.Add(tag);
        this.SelectedAudiofile.TagEnabled += OnAudiofileTagChanged;
    }

    private void OnAudiofileTagChanged()
    {
        var tag = new Tag() { IsEnabled = true };
        tag.EnabledChanged += OnAudiofileTagChanged;

        this.SelectedAudiofile.LocationTags.Add(tag);
    }
    ...

    public Audiofile SelectedAudiofile { get;set; }
}

In order to notify the UI about collection changes, the LocationTags property should be a ObservableCollection<T>:

public class Audiofile : INotifyPropertyChanged 
{
    public AudioFile()
    {
        this.LocationTags = new ObservableCollection<Tag>();
    }        

    public ObservableCollection<Tag> LocationTags
    {
        get => _locationTags;
        set
        {
            SetProperty(ref _locationTags, value);
            HasChanges = true;
        }
    }
}

The Tag implements a TagEnabled event:

public class Tag : INotifyPropertyChanged
{    
    public event EventHandler TagEnabled;
    private void OnTagIsEnabledChanged() => this.TagEnabled?.Invoke(this, EventArgs.Empty);

    private bool isEnabled;   
    public bool IsEnabled
    {
      get => this.isEnabled;
      set 
      { 
        this.isEnabled = value; 
        OnPropertyChanged();

        OnTagIsEnabledChanged();
      }
    }

    /// <summary>
    /// A User friendly name
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// The key which is tagged, e.g. charts which is then encoded into [charts]
    /// </summary>
    public string Key { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The item binding:

<ItemsControl ItemsSource="{Binding AppSettings.LocationTags}">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type Tag}">
            <StackPanel Orientation="Horizontal">
                <CheckBox IsChecked="{Binding IsEnabled}" />
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • This is not related to the question. I want to know how I can bind based on another list, checking if it contains a certain item – DirtyNative Jun 05 '20 at 16:20
  • You probably didn't make yourself clear then because it seems to me that this answers at least part of the question since you can set the IsEnabled property when you initialize the list. – Ostas Jun 05 '20 at 16:28
  • @DanielDirtyNativeMartin Like @Ostas said, you only have to iterate over the collection and set `Tag.IsEnabled` to `true` when you want check the value in the view. Regarding adding a new item when a `CheckBox` is checked by the user, you have to add more details. What do you want to add, a default `Tag`? You said `IsChecked` state changed. Does this include from `true` to `false`? – BionicCode Jun 05 '20 at 16:46
  • I don't need an "IsEnabled" Property, this is completly senseless. The list Audiofile.LocationTags gets generated by the Audiofiles comment. So when I show my UI, the ItemsControl generates a list of checkboxes based on AppSettings.LocationTags. But I want the checkboxes to be enabled based on Audiofile.LocationTags. So implementing a additional property doesnt result in any success. – DirtyNative Jun 05 '20 at 16:52
  • The property is not senseless. You must understand that it is bound to the `CheckBox` to allow controlling the state. It seems that you are completely unaware of your problem otherwise you wouldn't call it _"completely senseless"_. It doesn't make sense to _you_. If you didn't get it you should've ask me to explain how this suggestion can help. The way you are maintaining two disconnected collections of `Tag` is the problem. You should eliminate the binding to the `AppSettings.LocationTags` collection. Can you please show the code where you generate the `AudioFile.LocationTags ` collection? – BionicCode Jun 07 '20 at 09:15