Search code examples
c#wpflistlistbox

Listbox Refuses to update/List does not have matching types


In short, I wanted to create an updatable Listbox, which would update upon choosing an option from a ComboBox

<ListBox Background="Transparent" Width="Auto" Height="Auto" HorizontalAlignment="Right" ScrollViewer.CanContentScroll="true" ItemsSource="{Binding Lessons, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <WrapPanel>
                                <TextBlock Text="{Binding LessonText, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
                                    <StackPanel>
                                        
                                     </StackPanel>
                                </TextBlock>
                                <Image Source="{Binding LessonImage, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" HorizontalAlignment="Center" Width="auto" MaxWidth="400"/>
                            </WrapPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>

Changing a value of a variable Lesson fires an action, which fills an object list with elements

 public class LessonData : BaseViewModel
  {
    public int _lessonID;
    public string _lessonText;
    public byte[] _lessonImage;

    public int LessonID { get { return _lessonID; } set { _lessonID = value; OnPropertyChanged(nameof(LessonID)); } }
    public string LessonText { get { return _lessonText; } set { _lessonText = value; OnPropertyChanged(nameof(LessonText)); } }
    public byte[] LessonImage { get { return _lessonImage; } set { _lessonImage = value; OnPropertyChanged(nameof(LessonImage)); } }
  }
public List<LessonData> editedLessons = new List<LessonData>();

 public List<LessonData> EditedLessons { get { return editedLessons; } set { editedLessons = value; OnPropertyChanged(nameof(EditedLessons)); } }

 public string Lesson { get { return _lesson; } set { _lesson = value; OnPropertyChanged(nameof(Lesson));
        if (Lesson == "None")
        {
          EditedLessons.Clear();
          IsLessonBeingEdited = false;
        }
        else
        {
          EditedLessons = lessonRepository.Obtain_Lesson_Content(Lesson);
          foreach (LessonData p in EditedLessons) { Console.WriteLine(p.LessonID, p.LessonText, p.LessonImage); }
          IsLessonBeingEdited = true;
        }
      } }

Here is a function from a repository

public List<LessonData> Obtain_Lesson_Content(string Lesson)
    {
      List<LessonData> lc = new List<LessonData>();
      using (var connection = GetConnection())
      using (var command = new SqlCommand())
      {
        connection.Open();
        command.Connection = connection;
        command.CommandText = "SELECT Id_Lesson_Content, Lesson_Text, Lesson_Image FROM [Lesson_Content] WHERE Id_Lesson_Title = (SELECT Id_Lesson FROM [Lesson_Title] WHERE Lesson_Title = @title)";
        command.Parameters.Add("@title", SqlDbType.NVarChar).Value = Lesson;
        using (var reader = command.ExecuteReader())
        {
          while (reader.Read())
          {
            LessonData model = new LessonData();
            model.LessonID = (int)reader["Id_Lesson_Content"];
            model.LessonText = reader["Lesson_Text"].ToString();
            if (reader["Lesson_Image"] != System.DBNull.Value)
              model.LessonImage = (byte[])reader["Lesson_Image"];
            else
              model.LessonImage = null;
            lc.Add(model);
          }
          reader.NextResult();
        }
        foreach (LessonData p in lc) { Console.WriteLine(p.LessonID, p.LessonText, p.LessonImage); }
        return lc;
      }
    }

However every time I fire the function I meet with the errors thrown off (in that case it's loading two objects, one with and one without an image):

System.Windows.Data Error: 1 : Cannot create default converter to perform 'two-way' conversions between types 'System.Byte[]' and 'System.Windows.Media.ImageSource'. Consider using Converter property of Binding. BindingExpression:Path=LessonImage; DataItem='LessonData' (HashCode=2419756); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property.; Value='System.Byte[]' BindingExpression:Path=LessonImage; DataItem='LessonData' (HashCode=2419756); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
System.Windows.Data Error: 1 : Cannot create default converter to perform 'two-way' conversions between types 'System.Byte[]' and 'System.Windows.Media.ImageSource'. Consider using Converter property of Binding. BindingExpression:Path=LessonImage; DataItem='LessonData' (HashCode=10026414); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')

But to me the weirdest part is that trying to write all the list objects in a console throws these three errors:

  • can't convert from int to char[],

  • from string to int

  • from byte[] to int

    foreach (LessonData p in lc) { Console.WriteLine(p.LessonID, p.LessonText, p.LessonImage); }

Like, why does it even happen? I had looked at my other identical/similar functions which work in the same fashion and none of them throws such compilation errors. On top of that, when I change order of object elements by putting LessonID as the last, it does not display errors, (however the only item displayed is LessonText).

Whatever is the reason, I believe it is hidden behind that error with reading the list.


Solution

  • The source of the error message is the binding expression

    Source="{Binding LessonImage, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
    

    While there is a built-in automatic type conversion from string, Uri and byte[] to ImageSource (the type of the Source property) there is no automatic conversion back to those types (except string) which would be required for a TwoWay Binding.

    A TwoWay Binding of the Source property is pointless in the first place, because an Image element never changes that property by itself. The same applies to the Text property binding of the TextBlock element.

    So just use a OneWay Binding, where setting UpdateSourceTrigger is also pointless, because it affects only TwoWay and OneWayToSource Bindings.

    Source="{Binding LessonImage}"
    

    Besides that, the source property of the ItemsSource binding must be a collection that implements the INotifyPropertyChanged interface, e.g. an ObservableCollection instead of a List:

    The property should also be readonly:

    public ObservableCollection<LessonData> EditedLessons { get; }
        = new ObservableCollection<Lesson>();
    

    The Binding is also not TwoWay, but should just be

    <ListBox ItemsSource="{Binding EditedLessons}" ...>
    

    because a ListBox never actively changes the value of its ItemsSource property. This does not mean that the collection referenced by the property can't change.