0

I have a listView of items which are 'Book' instances, and when I click on a book the combobox should display its keywords; in fact it's a little trickier : the combobox contains the list of all the keywords of all books (duplicates removed)(the comboboxItems are checkboxes), and those of the selected book are checked. here is the multibinding:

<ComboBox
    x:Name="cbb_Keywords"
    Grid.Column="2"
    Width="300"
    Margin="5,0,0,0"
    HorizontalAlignment="Left"
    ItemsSource="{Binding Source={StaticResource AllBooks}}"
    DataContext="{Binding ElementName=listBoxBooks,Path=SelectedItem,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">

    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox Width="200">
                    <CheckBox.IsChecked>
                        <MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
                            <Binding Path="KeywordsForTextbox"></Binding>
                            <Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
                        </MultiBinding>
                    </CheckBox.IsChecked>
                </CheckBox>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

When I run my program, it seems ok When I click on a book, but I get an exception when I click on the combobox : impossible cast from 'MS.Internal.NamedObject' to 'System.String' type. I saw that value[0] is UnsetValue.

At debugging, when I use spies to track the value of WpfApp1.App.Books[0].KeywordsForTextbox, it gives me the good value (a string which is the list of the keywords of Book[0]. maybe the problem comes from listboxBooks.SelectedItem.KeywordsForTextBox? I can't spy in VS the value of 'listboxBooks'.

some related content... the beginning of the constructor of MainWindow:

public MainWindow()
{
    InitializeComponent();
    listBoxBooks.ItemsSource = App.Books;

the convert method of the converter:

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
    var check = false;
    if ((values != null && values.Length == 2))
    {
        string listString = (string)values[0];
        string wordToFind = (string) values[1];
        if ((listString != null))
        {
            List<string> keywordsList = listString.Split(',').ToList();
            if (keywordsList.Contains(wordToFind)) check = true;
        }       
    }

    return check;
}

the KeywordsForTextbox method:

public string KeywordsForTextbox
{
    get { return string.Join(",", _keywords); }
}

and finally the implementation of AllBooks:(as a window resource)

<ObjectDataProvider
    x:Key="AllBooks"
    MethodName="listOfAllKeywords"
    ObjectType="{x:Type mangmt:BookManagement}" />

thank you.

Gudarzi
  • 486
  • 3
  • 7
  • 22
lolveley
  • 1,659
  • 2
  • 18
  • 34
  • 1
    First, i assume that that the Convert method of your TextInListTrueFalseConverter throws the exception. So, is the exception happening when casting `values[0]` to a string, or when casting `values[1]`? I would guess it is the latter (the value coming from ``). My recollection about how item objects are associated with item visuals is a bit rusty, but what happens if you use a slightly different binding (referring to the DataContext of the item visual itself): ``? –  Nov 10 '18 at 15:48
  • no, value[0] is the one that throws the exception, the value being DependencyProperty.Unset, a placeholder indicating that there was a problem with the binding. but why, that's my question. – lolveley Nov 10 '18 at 16:42
  • 1
    Ah, my apologies. I missed the sentence where you mentioned value[0]... Note that your item source for the ComboBox is `AllBooks`. Now, don't know precisely what AllBooks is aside it being of type `BookManagement` (some collection with some entries of some type, i suppose). But, obviously, the combobox items would be elements from this BookManagement collection. And thus the _KeywordsForTextbox_ binding would attempt to find the _KeywordsForTextbox_ in the elements of the BookManagement collection. (1/2) –  Nov 10 '18 at 17:01
  • 1
    I noticed you talk about _KeywordsForTextbox_ only in conjunction with `App.Books`, but never in conjunction with `AllBooks`. If the _KeywordsForTextbox_ property is only provided by the object in listBoxBooks.SelectedItem, you cannot just set the DataContext of the combobox and then be done with it (as the binding in the item template will by default bind against the DataContext of the respective item visual, which is an item object from Combobox.ItemsSource). If you need the items template bind against data "outside" of the resepective item object, you need to specify the binding source(2/2) –  Nov 10 '18 at 17:05
  • If you need to bind against a property of a control outside the item template (whether it would be the ComboBox.DataContext or the ListBox.SelectedItem directly), you could use a _RelativeSource_ binding with _FindAncestor_ mode; similarly to what has been done here: https://stackoverflow.com/questions/30917025/bind-a-property-that-is-outside-of-an-itemscontrol-in-xaml (The End) –  Nov 10 '18 at 17:06
  • 1
  • @SimonEvans : plz write it as an answer and I will accept this as correct. it worked. thank you, and thank to elgonzo too! But I still have a problem with the behavior on the click of the checkbox. I will create a new post for this problem. – lolveley Nov 12 '18 at 14:47

1 Answers1

1

The first Binding of the Multi should be to be to the SelectedItem in the ListBox of Books. I have added in the <CheckBox.IsChecked> where appropriate, and Content="{Binding}" to the CheckBox:

<ComboBox.ItemTemplate>
    <DataTemplate>
        <StackPanel Orientation="Horizontal">
            <CheckBox Width="200" Content={Binding}>
              <CheckBox.IsChecked>
                <MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
                    <Binding ElementName=listBoxBooks, Path=SelectedItem.KeywordsForTextbox"></Binding>
                    <Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
                </MultiBinding>
              </CheckBox.IsChecked>
            </CheckBox>
        </StackPanel>
    </DataTemplate>
</ComboBox.ItemTemplate>

You may also wish to add some validation to the IMultiValueConverter to make sure the passed values are not unset, to avoid an exception: If Not values(0) Is DependencyProperty.UnsetValue And Not values(1) Is DependencyProperty.UnsetValue Then in VB.

Regarding the behaviour on checking the checkbox, I am guessing this is because of the ConvertBack Method of the IMultiValueConverter. You can remove the 'Throw Exception' code, and write a method to add/remove the text of the checked/unchecked box to your keyword list.

Gudarzi
  • 486
  • 3
  • 7
  • 22
Simon Evans
  • 238
  • 2
  • 10
  • I tried an other option, maybe worse : https://stackoverflow.com/questions/53265084/listview-not-refreshed-when-using-databinding . do you think it is possible to modify the listview's selectedItem directly from the convertBack method? – lolveley Nov 12 '18 at 15:19
  • Sorry for delay - and saw you have an answer that seems to be working for you, but yes, I think you could use the ConvertBack function to add the CheckBox Content to your List of words. I'd just check the list to see if the word is already included. If it is replace with an empty string, if it is not then add it. So long as the List Property implements INotifyPropertyChanged then your text box should be updated too. You may even consider making your list of keywords an observable collection rather than a string - most of what you're doing is list operations, just convert to string for TextBox – Simon Evans Nov 13 '18 at 19:51