DataGridComboBoxColumn 的 IValueConverter 错误

IValueConverter error for DataGridComboBoxColumn

所以我有一个 DataGridComboBoxColumn ColCID 其值取决于 ColSID 行的另一个单元格(见下面的代码)。我尝试使用 IValueConverter 来实现它,但出现此错误:

Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:(no path); DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=18018639); target property is 'ItemsSource' (type 'IEnumerable')

XAML:

<DataGridComboBoxColumn x:Name="ColSID" Header="Guild"
                        SelectedValueBinding="{Binding SID, Mode=TwoWay}"
                        SelectedValuePath="SID"
                        DisplayMemberPath="Name" />
<DataGridComboBoxColumn x:Name="ColCID" Header="Channel"
                        ItemsSource="{ Binding ElementName=ColSID, Converter={StaticResource ChannelConverter} }"
                        SelectedValueBinding="{Binding CID, Mode=TwoWay}"
                        SelectedValuePath="CID"
                        DisplayMemberPath="Name" />

转换器:

public class ChannelConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Guild guild = SenderView.Guilds.Find(g => g.SID == value.ToString());
        if (guild != null) return guild.Channels;
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
...
public class Guild
{
    public class Channel
    {
        public string CID { get; set; }
        public string Name { get; set; }
    }
    public string SID { get; set; }
    public string Name { get; set; }
    public List<Channel> Channels { get; set; }
}

在我看来,对于网格中的每一行,您希望用户能够 select GuildChannel Guild .每个 Guild 都有自己的 collection 个 Channel,所以我们将从那个 collection 到 select。我要绑定 Channel 和 Guild object,而不仅仅是他们的 ID。如果您愿意,我们可以采用其他方式,但我发现让 ComboBox 查找 object 比我的其余代码必须这样做更容易。当我需要时,SID 或 CID 就在 selected object 上。

以下是您的操作方法。您不能按照惯例将任何内容绑定到 XAML 中的 DataGridComboBoxColumn.ItemsSource。涉及 属性 的任何解决方案充其量都将有点奇特,但完全没有必要:您可以通过 ElementStyle 和 EditingElementStyle 绑定 ComboBox 的 ItemsSource。 SelectedValuePathSelectedValueBinding 确实有效,但我没有使用它们。

我为 parent 视图模型编写了一个快速 stand-in,它拥有 collection 的公会,用户可以从中 select。请注意,我还将您的 Channel class 移出了 Guild。我这样做的唯一原因是我可以在 Guild class 上为 selected 频道设置 Channel 属性。如果您更愿意将 Channel 保留在原来的位置,只需将 GuildChannel 属性 重命名为 SelectedChannel 或类似名称,然后更改绑定在 XAML 相应地。

"framework mentor" 废话是因为列不是可视化树中的 child 控件。它们是对 DataGrid 创建列 header 以及每行中的单元格的指令。那些 header 和单元格,以及它们的模板化内容,都在可视化树中。 SelectedItemBinding 不是对列的绑定;这是一个绑定,列创建代码将 设置在 ComboBox 的 SelectedItem 属性 上,它最终将在单元格内容中创建。但是 ItemsSource 属性 只是列本身的 属性 。您可以绑定它 with a binding proxy,人们也这样做,但绑定代理是懦夫的出路。

XAML

<DataGrid
    ItemsSource="{Binding Selections}"
    AutoGenerateColumns="False"
    Grid.Row="1"
    >
    <DataGrid.Resources>
        <Style TargetType="ComboBox" x:Key="GuildComboStyle">
            <Setter 
                Property="ItemsSource" 
                Value="{Binding DataContext.Guilds, RelativeSource={RelativeSource AncestorType=DataGrid}}" 
                />
        </Style>
        <Style 
            TargetType="ComboBox" 
            x:Key="ChannelComboStyle"
            >
            <!-- Our DataContext here is a GuildSelection object, so we look at its Guild
            property for a collection of Channels to use.
            -->
            <Setter  Property="ItemsSource" Value="{Binding Guild.Channels}" />
            <!-- If there's no selected Guild, prompt the user to select one. -->
            <Style.Triggers>
                <DataTrigger Binding="{Binding Guild}" Value="{x:Null}">
                    <Setter Property="IsEnabled" Value="False" />
                    <Setter Property="IsEditable" Value="True" />
                    <Setter Property="Text" Value="Please select a Guild" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridComboBoxColumn 
            Header="Guild"
            SelectedItemBinding="{Binding Guild, UpdateSourceTrigger=PropertyChanged}"
            DisplayMemberPath="Name" 
            ElementStyle="{StaticResource GuildComboStyle}"
            EditingElementStyle="{StaticResource GuildComboStyle}"
            Width="200"
            />
        <DataGridComboBoxColumn 
            Header="Channel"
            SelectedItemBinding="{Binding Guild.Channel, UpdateSourceTrigger=PropertyChanged}"
            DisplayMemberPath="Name" 
            ElementStyle="{StaticResource ChannelComboStyle}"
            EditingElementStyle="{StaticResource ChannelComboStyle}"
            Width="200"
            />

        <DataGridTextColumn Binding="{Binding Guild.SID}" Header="Guild SID" />
        <DataGridTextColumn Binding="{Binding Guild.Name}" Header="Guild Name" />
        <DataGridTextColumn Binding="{Binding Guild.Channel.CID}" Header="Channel CID" />
        <DataGridTextColumn Binding="{Binding Guild.Channel.Name}" Header="Channel Name" />
    </DataGrid.Columns>
</DataGrid>

C#

public class MainViewModel
{
    public ObservableCollection<GuildSelection> Selections { get; set; }
    public ObservableCollection<Guild> Guilds { get; set; }
}

public class GuildSelection : ViewModelBase
{
    #region Guild Property
    private Guild _guild = null;
    public Guild Guild
    {
        get { return _guild; }
        set
        {
            if (value != _guild)
            {
                _guild = value;
                OnPropertyChanged();
            }
        }
    }
    #endregion Guild Property
}

public class Channel
{
    public string CID { get; set; }
    public string Name { get; set; }
}

public class Guild : ViewModelBase
{
    public string SID { get; set; }
    public string Name { get; set; }
    public List<Channel> Channels { get; set; }

    #region Channel Property
    private Channel _channel = null;
    public Channel Channel
    {
        get { return _channel; }
        set
        {
            if (value != _channel)
            {
                _channel = value;
                OnPropertyChanged();
            }
        }
    }
    #endregion Channel Property
}

#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(
        [System.Runtime.CompilerServices.CallerMemberName] string propName = null) 
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    #endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class

尝试使用 ElementStyle 并绑定到 SID 属性,当您 select ColSID 中的项目时设置:

<DataGridComboBoxColumn x:Name="ColSID" Header="Guild"
                        SelectedValueBinding="{Binding SID, Mode=TwoWay}"
                        SelectedValuePath="SID"
                        DisplayMemberPath="Name" />
<DataGridComboBoxColumn x:Name="ColCID" Header="Channel"
                        SelectedValueBinding="{Binding CID, Mode=TwoWay}"
                        SelectedValuePath="CID"
                        DisplayMemberPath="Name">
    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="ComboBox">
            <Setter Property="ItemsSource" Value="{Binding SID, Converter={StaticResource ChannelConverter}}" />
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="ComboBox">
            <Setter Property="ItemsSource" Value="{Binding SID, Converter={StaticResource ChannelConverter}}" />
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>