web-dev-qa-db-ja.com

編集可能なDataGridComboBoxColumnをWPFDataGridに実装する方法

ユーザーが(.net Framework 4.0から)WPFDataGridの一部のデータを編集できるようにしたい。 「機器」列では、ユーザーが静的リストから使用可能な機器を選択したり、フリーテキストを書き込んだりできるようにする必要があります。私のDataGridはMVVMを使用してデータにバインドされています。私はインターネットで見つけた多くの解決策を試しましたが、どれも正しく機能しません。これが私のコードです:

<DataGrid Margin="0,6" ItemsSource="{Binding Path=Orders}" AutoGenerateColumns="False"  CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="True">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Instrument" MinWidth="140"                                      
 ItemsSource="{x:Static ViewModel.Instruments}" SelectedItemBinding="{Binding Path=SelectedInstrument}">
 <DataGridComboBoxColumn.EditingElementStyle>
   <Style TargetType="ComboBox">
     <Setter Property="IsEditable" Value="True"/>
   </Style>                  
 </DataGridComboBoxColumn.EditingElementStyle>                
</DataGridComboBoxColumn>   
</DataGrid.Columns>
</DataGrid>

ドロップダウンリストが正しく表示されます。このフィールドは任意のテキストで編集できますが、フリーテキストのドロップダウンを閉じた後、SelectedInstrumentにnullが設定されます。選択したアイテムに対してのみ機能します。 SelectedValueBindingに変更しようとしましたが、役に立ちません。

この要件を適切に実装するにはどうすればよいですか?誰かがここに実用的なサンプルを投稿できますか?

Additional:Orders is ObservableCollection Orderには、文字列Title、DateTime Ordered、string SelectedInstrument、Instruments is a string []などのプロパティがあります。

解決策bathineni の回避策として次の提案が機能します:

<DataGrid Margin="0,6" ItemsSource="{Binding Path=Orders}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="True">
 <DataGrid.Columns>
  <DataGridTemplateColumn Header="Instrument" MinWidth="140">
   <DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
     <TextBlock Text="{Binding Path=SelectedInstrument, Mode=OneWay}"/>
    </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
   <DataGridTemplateColumn.CellEditingTemplate>
    <DataTemplate>
     <ComboBox IsEditable="True" Text="{Binding Path=SelectedInstrument}" 
      ItemsSource="{x:Static ViewModel.Instruments}"/>                   
    </DataTemplate>
   </DataGridTemplateColumn.CellEditingTemplate>
  </DataGridTemplateColumn>   
 </DataGrid.Columns>
</DataGrid>
21

これは、入力されたフリーテキストが文字列型であり、comboBoxにバインドした選択されたアイテムが複雑な型であるために発生しています。

DataGridComboBoxColumnを使用する代わりにDataGridTemplateColumnを使用すると、comboBoxのTextプロパティを、ドロップダウンリストを閉じた後にフリーテキスト値を保持するプロパティにバインドできます。

次のサンプルを見ると、より良いアイデアを得ることができます。

<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox IsEditable="True" 
                              Text="{Binding NewItem}" 
                              ItemsSource="{Binding Sourcelist.Files}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>
17
Bathineni

SelectedValueのみを使用してみてください。ただし、それに加えてDisplayMemberPathとTextSearch.TextPathを使用してください。

   <ComboBox IsEditable="True" DisplayMemberPath="MyDisplayProperty" SelectedValuePath="MyValueProperty" SelectedValue="{Binding MyViewModelValueProperty}" TextSearch.TextPath="MyDisplayProperty" />

編集可能なコンボボックスの場合、コンボが選択する値、アイテムが表示する値、およびユーザー入力に基づいて検索する必要がある値を同期する必要があります。

ただし、文字列コレクションを使用してコンボボックスをバインドしている場合は、次のことを試すことができます...

  1. InstrumentsViewという名前の新しいプロパティをViewModelに追加します。これにより、新しいListCollectionViewが返されます。

    public static string ListCollectionView InstrumentsView
    {
            get
            {
                    return new ListCollectionView(Instruments);
            }
    }
    
  2. DataGridComboBoxColumnXAMLを次のように変更します...

    <DataGridComboBoxColumn Header="Instrument" MinWidth="140"
                            ItemsSource="{x:Static ViewModel.InstrumentsView}">
            <DataGridComboBoxColumn.EditingElementStyle>
                    <Style TargetType="ComboBox">
                            <Setter Property="IsEditable" Value="True"/>
                            <Setter Property="IsSynchronizedWithCurrentItem" Value=True" />
                            <Setter Property="SelectedItem" Value="{Binding SelectedInstrument, Mode=OneWayToSource}" /> <!-- Assuming that SelectedInstrument is string  -->
                    </Style>
            </DataGridComboBoxColumn.EditingElementStyle>
    </DataGridComboBoxColumn>
    

これがうまくいくかどうか教えてください...

6
WPF-it

DataGridBoundColumnをサブクラス化することにより、独自のComboBox列タイプを作成できます。 DataGridTemplateColumnをサブクラス化するbathineniのソリューションと比較すると、以下のソリューションには、ユーザーエクスペリエンスが向上し(ダブルタブがない)、特定のニーズに合わせて列を調整するためのオプションが増えます。

public class DataGridComboBoxColumn : DataGridBoundColumn {
    public Binding ItemsSourceBinding { get; set; }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
        var textBox = new TextBlock();
        BindingOperations.SetBinding(textBox, TextBlock.TextProperty, Binding);
        return textBox;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
        var comboBox = new ComboBox { IsEditable = true };
        BindingOperations.SetBinding(comboBox, ComboBox.TextProperty, Binding);
        BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, ItemsSourceBinding);
        return comboBox;
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs) {
        var comboBox = editingElement as ComboBox;
        if (comboBox == null) return null;

        comboBox.Focus(); // This solves the double-tabbing problem that Nick mentioned.
        return comboBox.Text;
    }
}

次に、たとえば次のようにコンポーネントを使用できます。

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyItems}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
        <local:DataGridComboBoxColumn Header="Thingy" Binding="{Binding Thingy}"
            ItemsSourceBinding="{Binding
                RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}},
                Path=Thingies}"/>
    </DataGrid.Columns>
</DataGrid>

私は同様の質問に この答え に従うことによってこの解決策を得ました。

4
vvnurmi