为什么使用 Convert 方法来 ConvertBack WPF 组合框中的值?

发布于 2024-11-18 13:35:06 字数 2864 浏览 11 评论 0原文

确切的问题是——为什么使用 Convert 而不是 ConvertBack,以及为什么这里首先需要 ConvertBack?

问题

下面是我的问题的示例,我试图简化事情。这是普通的 WPF,没有第 3 方库,问题本身是经典的主从细节,但有一点扭曲。

我有列表框(城市列表)和数据网格(列出了我所有的朋友+电话)。当我选择列表框时,数据网格应该刷新并显示来自所选城市的人。数据网格如下所示:

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

这里的关键问题是 PhoneType 列。每个单元格应该是一个包含预定义电话类型(从数据库获取)的组合框。问题在于数据库结构。它是这样的:

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

在我的数据网格组合框中,应该显示 PhoneType,但是如果存在 PhoneDescription,则应该使用它而不是普通的 PhoneType。

问题结束。你和我在一起吗?

实现

为了在数据网格中有组合框,我必须使用内部带有组合框的模板列,而不是组合框数据网格列(原因如下:WPF Datagrid ComboBox DataBinding)。这是我的组合框:

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

好的,在这里(上面)我定义了组合框下拉列表中应列出的内容。 PhoneType 记录列表(在数据库意义上)。

          SelectedValuePath="typeID"
          SelectedValue="{Binding Path=typeID}">

组合框应该知道我朋友的电话类型的当前值,因此这里(上面)是我绑定这些值的方式 - 一方面我设置了 PhoneType 记录中的 typeID 应该用于匹配,另一方面我设置应该使用好友记录中的 typeID。这样,WPF 就知道应将电话类型的哪个记录用作当前值。

(如果您不是 100% 熟悉此绑定,这里有一个很好的解释: SelectedItem、SelectedValue 和 SelectedValuePath 之间的差异

顺便说一句。 typeID 使用两次,因为一般来说,我更喜欢对数据库中的相关字段(外键字段)使用完全相同的名称。

我有列表,我已经完成匹配,现在 - 为了显示,我不能只使用DisplayMemberPath 因为我需要更动态的东西。

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

因此,为了显示 PhoneType,我获取整个记录并选择适当的字符串。

[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;
    }
}

错误

我编译整个应用程序,运行它,单击任何城市。它按预期工作,很棒。

然后我点击另一个城市。应用程序思考了一下,然后在刷新城市列表框或数据网格之前,它崩溃并出现异常(请参见上面的标记行),并显示:

无法转换类型的对象 要输入的“System.String” 'MyBuilderApp.PhoneTypeRecord'。

我不知道发生了什么事。为什么将字符串传递给Convert???它看起来更像是ConvertBack。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

喜爱皱眉﹌ 2024-11-25 13:35:06

我认为在这种情况下,使用 优先级绑定。它将处理描述为空的情况,并为您使用该类型:

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

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>
一绘本一梦想 2024-11-25 13:35:06

正如我所见,文本块绑定到从 PhoneTypesList 返回的内容。然后使用转换器,它需要一个 PhoneTypeRecord。
我想 PhoneTypesList 是 PhoneTypeRecord 的列表,对吧?
我注意到,在转换器中,我得到的是完整列表,而不是列表中的每个元素。所以你实际上得到了 PhoneTypeRecord 的列表/集合/observableCollection。验证一下。

您还可以在转换器中添加如下条件:

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

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");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文