开发者

Why Convert method is used to ConvertBack a value in WPF combobox?

The exact question is -- why Convert is used instead of ConvertBack, and why really ConvertBack is needed here in the first place?

Problem

Below is EXAMPLE of my problem, I tried to simplify things. This is plain WPF, no 3rd party libraries, the problem itself is classic master-detail with a little twist.

I have listbox (list of cities) and datagrid (which lists all my friends + telephones). When I select listbox, datagrid should refresh and show the guys from the selected city. The datagrid looks like this:

Name | Phone | PhoneType
Mark | 76447 | cellphone
...

The key issue here is PhoneType column. Each cell should be a combobox filled with predefined phone types (fetched from database). And the twist lies with the db structure. It is something like this:

typeID | PhoneType | PhoneDescription
1      | cellphone | NULL
2      | neighbour | call only in case of emergency

In my datagrid combobox PhoneType should be displayed BUT if PhoneDescription is present, it should be used instead of plain PhoneType.

End of the problem. You with me?

Implementation

In order to have combobox in datagrid I have to use template column with combobox inside, instead of combobox datagrid column (here is why: WPF Datagrid ComboBox DataBinding). So here it is my combobox:

<ComboBox ItemsSource="{Binding Path=PhoneTypesList, 
          RelativeSource={RelativeSource AncestorType={x:Type Window}}}"  

Ok, here (above) I define what should be listed in Combobox dropdown list. A list of PhoneType records (in database sense).

          SelectedValuePath="typeID"
      开发者_运维百科    SelectedValue="{Binding Path=typeID}">

Combobox should be aware of the current value of phone type of my friend so here (above) is how I bind those values -- on one hand I set that typeID from PhoneType record should be used for matching, and on the other hand I set that typeID from Friend record should be used. This way WPF knows which record of phone types should be used as current value.

(If you are not 100% familiar with this binding, here is nice explanation: Difference between SelectedItem, SelectedValue and SelectedValuePath )

Btw. typeID is used twice because in general I prefer using exactly the same name for related fields (foreign key fields) in database.

I have the list, I have the matching done, now -- for displaying I cannot use just a DisplayMemberPath because I need something more dynamic.

  <ComboBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding Converter={StaticResource PhoneTypeConverter}}"/>
    </DataTemplate>
  </ComboBox.ItemTemplate>
</ComboBox>

So to display PhoneType, I get entire record and choose appropriate string.

[ValueConversion(typeof(PhoneTypeRecord), typeof(string))]
public class PhoneTypeConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        if (value == null)
            return null;

        var record = ((PhoneTypeRecord)value); // crash!
        return record.PhoneDescription ?? record.PhoneType;
    }

    // we don't do any conversion back
    public object ConvertBack(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
            return null;
    }
}

Error

I compile entire application, I run it, I click on any city. It works as expected, great.

Then I click on ANOTHER city. And the application thinks a bit, and then before city listbox or datagrid is refreshed it crashes with exception (see the marked line above) saying:

Unable to cast object of type 'System.String' to type 'MyBuilderApp.PhoneTypeRecord'.

And with this I have no clue what is going on. Why string is passed to Convert??? It looks more like ConvertBack.


I think in this case, it would be a lot easier on you to use a PriorityBinding. It will handle the case where the description is null, and use the type for you:

<ComboBox>
  <ComboBox.ItemTemplate>
    <DataTemplate>
      <TextBlock>
         <TextBlock.Text>
           <PriorityBinding>
             <Binding Path="PhoneDescription" />
             <Binding Path="PhoneType" />
           </PriorityBinding>
         </TextBlock.Text>
      </TextBlock>
    </DataTemplate>
  </ComboBox.ItemTemplate>
</ComboBox>


As I see it, textblock is binding to what is returned from the PhoneTypesList. And then is uses the converter on this, which expects a PhoneTypeRecord. I suppose that PhoneTypesList is a list of PhoneTypeRecord right ? What I've noticed is that in the converter, I get the full list not every element in the list. So you actually get a list/collection/observableCollection of PhoneTypeRecord. Verify it.

You can also add a condition like this in the converter:

if(value != typeof(what_you_are_expecting) throw new InvalidOperationException("something is wrong");
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜