Các mục liên kết Nguồn của một ComboBoxColumn trong WPF DataGrid


82

Tôi có hai lớp Model đơn giản và ViewModel ...

public class GridItem
{
    public string Name { get; set; }
    public int CompanyID { get; set; }
}

public class CompanyItem
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public class ViewModel
{
    public ViewModel()
    {
        GridItems = new ObservableCollection<GridItem>() {
            new GridItem() { Name = "Jim", CompanyID = 1 } };

        CompanyItems = new ObservableCollection<CompanyItem>() {
            new CompanyItem() { ID = 1, Name = "Company 1" },
            new CompanyItem() { ID = 2, Name = "Company 2" } };
    }

    public ObservableCollection<GridItem> GridItems { get; set; }
    public ObservableCollection<CompanyItem> CompanyItems { get; set; }
}

... và một Cửa sổ đơn giản:

<Window x:Class="DataGridComboBoxColumnApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}" />
                <DataGridComboBoxColumn ItemsSource="{Binding CompanyItems}"
                                    DisplayMemberPath="Name"
                                    SelectedValuePath="ID"
                                    SelectedValueBinding="{Binding CompanyID}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

ViewModel được đặt thành MainWindow DataContexttrong App.xaml.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        ViewModel viewModel = new ViewModel();

        window.DataContext = viewModel;
        window.Show();
    }
}

Như bạn có thể thấy, tôi đặt ItemsSourceDataGrid thành GridItemsbộ sưu tập của ViewModel. Phần này hoạt động, dòng Grid duy nhất có Tên "Jim" được hiển thị.

Tôi cũng muốn đặt ItemsSourceComboBox ở mọi hàng thành CompanyItemsbộ sưu tập của ViewModel. Phần này không hoạt động: ComboBox vẫn trống và trong Cửa sổ đầu ra của trình gỡ lỗi, tôi thấy thông báo lỗi:

Lỗi System.Windows.Data: 2: Không thể tìm thấy FrameworkElement quản lý hoặc FrameworkContentElement cho phần tử đích. BindingExpression: Path = CompanyItems; DataItem = null; phần tử đích là 'DataGridComboBoxColumn' (HashCode = 28633162); thuộc tính mục tiêu là 'ItemsSource' (nhập 'IEnumerable')

Tôi tin rằng WPF mong đợi CompanyItemslà một tài sản GridItemkhông phải như vậy và đó là lý do tại sao ràng buộc không thành công.

Tôi đã cố gắng làm việc với một RelativeSourceAncestorTypenhư vậy:

<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems, 
    RelativeSource={RelativeSource Mode=FindAncestor,
                                   AncestorType={x:Type Window}}}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="ID"
                        SelectedValueBinding="{Binding CompanyID}" />

Nhưng điều đó mang lại cho tôi một lỗi khác trong đầu ra của trình gỡ lỗi:

Lỗi System.Windows.Data: 4: Không thể tìm thấy nguồn để liên kết với tham chiếu 'RelativeSource FindAncestor, AncestorType =' System.Windows.Window ', AncestorLevel =' 1 ''. BindingExpression: Path = CompanyItems; DataItem = null; phần tử đích là 'DataGridComboBoxColumn' (HashCode = 1150788); thuộc tính mục tiêu là 'ItemsSource' (nhập 'IEnumerable')

Câu hỏi: Tôi làm cách nào để liên kết Nguồn mục của DataGridComboBoxColumn với bộ sưu tập CompanyItems của ViewModel? Có thể ở tất cả?

Cảm ơn bạn đã giúp đỡ trước!

Câu trả lời:


123

Vui lòng kiểm tra xem DataGridComboBoxColumn xaml dưới đây có phù hợp với bạn không:

<DataGridComboBoxColumn 
    SelectedValueBinding="{Binding CompanyID}" 
    DisplayMemberPath="Name" 
    SelectedValuePath="ID">

    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

Tại đây, bạn có thể tìm thấy giải pháp khác cho vấn đề bạn đang gặp phải: Sử dụng hộp tổ hợp với WPF DataGrid


4
Chết tiệt, nó hoạt động !!! Nếu tôi chỉ có thể hiểu tại sao? Và tại sao không phải là mã gốc với những thay đổi mà Rachel đề xuất? Dù sao, cảm ơn bạn rất nhiều!
Slauma

1
Tôi tin rằng bạn có thể tìm thấy lời giải thích ở đây: wpf.codeplex.com/workitem/8153?ProjectName=wpf (xem nhận xét)
serge_gubenko

1
Dường như họ đã quyết định biến lỗi này ("Chúng tôi đã gửi một lỗi trong cơ sở dữ liệu nội bộ của chúng tôi để được sửa trong bản phát hành trong tương lai.") Thành một tính năng. Hãy xem câu trả lời của riêng tôi trong chủ đề này: Vấn đề đã được giải quyết bằng tài liệu, một dấu hiệu mạnh mẽ rằng điều này sẽ không bao giờ thay đổi.
Slauma

1
+1 cho liên kết joemorrison.org/blog/2009/02/17/… . đã giải quyết được vấn đề của tôi. Sucks, ~ 5 giờ và tôi nhận ra tôi đã có loại này trong dự án của tôi cho cái gì khác chúng tôi đã làm :( nó luôn luôn là một quá trình học tập.
TravisWhidden

Không hiệu quả với tôi. E EditElementStyle dường như hoạt động, nhưng vì một số lý do khi tôi thêm ElementStyle, tôi nhận được các ComboBox không điền bất kỳ thứ gì (thay vì giá trị từ DisplayMemberPath) và nó sẽ không chuyển trở lại E EditorElementStyle khi tôi nhấp vào.
William

46

Các tài liệu trên MSDN về ItemsSourcecủaDataGridComboBoxColumn nói rằng các nguồn lực chỉ tĩnh, tĩnh mã hoặc inline bộ sưu tập các mặt hàng combobox có thể được ràng buộc với ItemsSource:

Để điền danh sách thả xuống, trước tiên hãy đặt thuộc tính ItemsSource cho ComboBox bằng cách sử dụng một trong các tùy chọn sau:

  • Một tài nguyên tĩnh. Để biết thêm thông tin, hãy xem Tiện ích mở rộng đánh dấu StaticResource.
  • Một x: Thực thể mã tĩnh. Để biết thêm thông tin, hãy xem x: Tiện ích mở rộng đánh dấu tĩnh.
  • Một tập hợp nội tuyến của các loại ComboBoxItem.

Không thể liên kết với thuộc tính của DataContext nếu tôi hiểu đúng về điều đó.

Và thực sự: Khi tôi tạo CompanyItemsmột thuộc tính tĩnh trong ViewModel ...

public static ObservableCollection<CompanyItem> CompanyItems { get; set; }

... thêm không gian tên nơi ViewModel được đặt vào cửa sổ ...

xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"

... và thay đổi ràng buộc thành ...

<DataGridComboBoxColumn
    ItemsSource="{Binding Source={x:Static vm:ViewModel.CompanyItems}}" 
    DisplayMemberPath="Name"
    SelectedValuePath="ID"
    SelectedValueBinding="{Binding CompanyID}" />

... sau đó nó hoạt động. Nhưng việc có ItemsSource làm thuộc tính tĩnh đôi khi có thể ổn, nhưng không phải lúc nào tôi cũng muốn.


1
tôi vẫn hy vọng microsoft sẽ sửa lỗi này
juFo

38

Giải pháp chính xác dường như là:

<Window.Resources>
    <CollectionViewSource x:Key="ItemsCVS" Source="{Binding MyItems}" />
</Window.Resources>
<!-- ... -->
<DataGrid ItemsSource="{Binding MyRecords}">
    <DataGridComboBoxColumn Header="Column With Predefined Values"
                            ItemsSource="{Binding Source={StaticResource ItemsCVS}}"
                            SelectedValueBinding="{Binding MyItemId}"
                            SelectedValuePath="Id"
                            DisplayMemberPath="StatusCode" />
</DataGrid>

Bố cục trên hoạt động hoàn toàn tốt đối với tôi và sẽ phù hợp với những người khác. Lựa chọn thiết kế này cũng có ý nghĩa, mặc dù nó không được giải thích rõ ràng ở bất kỳ đâu. Nhưng nếu bạn có một cột dữ liệu với các giá trị được xác định trước, những giá trị đó thường không thay đổi trong thời gian chạy. Vì vậy, việc tạo CollectionViewSourcevà khởi tạo dữ liệu một lần có ý nghĩa. Nó cũng loại bỏ các ràng buộc dài hơn để tìm tổ tiên và ràng buộc trên ngữ cảnh dữ liệu của nó (điều mà tôi luôn cảm thấy sai lầm).

Tôi để điều này ở đây cho bất kỳ ai khác đang vật lộn với ràng buộc này và tự hỏi liệu có cách nào tốt hơn không (Vì trang này rõ ràng vẫn xuất hiện trong kết quả tìm kiếm, đó là cách tôi đến đây).


1
Mặc dù được cho là một câu trả lời hay, nhưng có lẽ nó đã được tóm tắt từ câu hỏi của OP. Của bạn MyItemssẽ dẫn đến lỗi biên dịch nếu được sử dụng với mã của OP
MickyD

22

Tôi nhận ra câu hỏi này đã hơn một năm, nhưng tôi chỉ tình cờ gặp nó khi giải quyết một vấn đề tương tự và nghĩ rằng tôi sẽ chia sẻ một giải pháp tiềm năng khác trong trường hợp nó có thể giúp ích cho một khách du lịch trong tương lai (hoặc chính tôi, khi tôi quên điều này sau đó và tìm thấy chính mình lộn xộn trên StackOverflow giữa tiếng la hét và ném vật thể gần nhất trên bàn của tôi).

Trong trường hợp của tôi, tôi có thể đạt được hiệu ứng mà tôi muốn bằng cách sử dụng DataGridTemplateColumn thay vì DataGridComboBoxColumn, một đoạn mã sau đây. [cảnh báo: Tôi đang sử dụng .NET 4.0 và những gì tôi đang đọc khiến tôi tin rằng DataGrid đã phát triển rất nhiều, vì vậy YMMV nếu sử dụng phiên bản trước đó]

<DataGridTemplateColumn Header="Identifier_TEMPLATED">
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox IsEditable="False" 
                Text="{Binding ComponentIdentifier,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                ItemsSource="{Binding Path=ApplicableIdentifiers, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ComponentIdentifier}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Sau khi đấu tranh với một vài câu trả lời đầu tiên, tôi đã thử điều này và nó cũng hiệu quả với tôi. Cảm ơn.
coson

7

RookieRick đã đúng, sử dụng DataGridTemplateColumnthay vì DataGridComboBoxColumnmang lại một XAML đơn giản hơn nhiều.

Hơn nữa, đặt CompanyItemdanh sách có thể truy cập trực tiếp từ GridItemcho phép bạn loại bỏ RelativeSource.

IMHO, điều này cung cấp cho bạn một giải pháp rất sạch sẽ.

XAML:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
    <DataGrid.Resources>
        <DataTemplate x:Key="CompanyDisplayTemplate" DataType="vm:GridItem">
            <TextBlock Text="{Binding Company}" />
        </DataTemplate>
        <DataTemplate x:Key="CompanyEditingTemplate" DataType="vm:GridItem">
            <ComboBox SelectedItem="{Binding Company}" ItemsSource="{Binding CompanyList}" />
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Name}" />
        <DataGridTemplateColumn CellTemplate="{StaticResource CompanyDisplayTemplate}"
                                CellEditingTemplate="{StaticResource CompanyEditingTemplate}" />
    </DataGrid.Columns>
</DataGrid>

Xem mô hình:

public class GridItem
{
    public string Name { get; set; }
    public CompanyItem Company { get; set; }
    public IEnumerable<CompanyItem> CompanyList { get; set; }
}

public class CompanyItem
{
    public int ID { get; set; }
    public string Name { get; set; }

    public override string ToString() { return Name; }
}

public class ViewModel
{
    readonly ObservableCollection<CompanyItem> companies;

    public ViewModel()
    {
        companies = new ObservableCollection<CompanyItem>{
            new CompanyItem { ID = 1, Name = "Company 1" },
            new CompanyItem { ID = 2, Name = "Company 2" }
        };

        GridItems = new ObservableCollection<GridItem> {
            new GridItem { Name = "Jim", Company = companies[0], CompanyList = companies}
        };
    }

    public ObservableCollection<GridItem> GridItems { get; set; }
}

4

ComboBox của bạn đang cố gắng liên kết để liên kết GridItem[x].CompanyItems, không tồn tại.

RelativeBinding của bạn đã đóng, tuy nhiên nó cần phải liên kết với DataContext.CompanyItemsvì Window.CompanyItems không tồn tại


Cảm ơn đã trả lời! Tôi đã thử điều đó (được thay thế CompanyItemsbằng DataContext.CompanyItemsđoạn mã XAML cuối cùng trong câu hỏi của tôi) nhưng nó mang lại cho tôi lỗi tương tự trong đầu ra của trình gỡ lỗi.
Slauma

1
@Slauma Sau đó, tôi không chắc, nó sẽ hoạt động. Điều bất thường duy nhất tôi thấy với XAML bạn có là Chế độ = FindAncestor và tôi thường bỏ qua điều đó. Bạn đã thử đặt Tên cho cửa sổ gốc của mình và tham chiếu nó bằng tên trong ràng buộc của bạn thay vì sử dụng RelativeSource chưa? {Binding ElementName=RootWindow, Path=DataContext.CompanyItems}
Rachel

Đã thử cả hai cách (bỏ qua Mode = FindAncestor và thay đổi ràng buộc thành một phần tử được đặt tên), nhưng nó không hoạt động. Kỳ lạ là cách này có hiệu quả với bạn. Tôi đã tạo ứng dụng thử nghiệm đơn giản này để kéo sự cố ra khỏi ứng dụng của mình vào một ngữ cảnh rất đơn giản. Tôi không biết mình có thể làm sai điều gì, mã bạn thấy trong câu hỏi là ứng dụng đầy đủ (được tạo từ mẫu dự án WPF trong VS2010), không có gì thêm xung quanh mã này.
Slauma

1

theo cách tôi sử dụng, tôi liên kết textblock và combobox với cùng một thuộc tính và thuộc tính này sẽ hỗ trợtifyPropertyChanged.

tôi đã sử dụng nguồn tương đối để liên kết với văn bản dữ liệu của chế độ xem chính, đây là quyền kiểm soát của người dùng để đi lên cấp dữ liệu trong liên kết vì trong trường hợp này, datagrid sẽ tìm kiếm trong đối tượng mà bạn đã sử dụng trong datagrid.itemsource

<DataGridTemplateColumn Header="your_columnName">
     <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
             <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit.Name, Mode=TwoWay}" />
           </DataTemplate>
     </DataGridTemplateColumn.CellTemplate>
     <DataGridTemplateColumn.CellEditingTemplate>
           <DataTemplate>
            <ComboBox DisplayMemberPath="Name"
                      IsEditable="True"
                      ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.UnitLookupCollection}"
                       SelectedItem="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                      SelectedValue="{Binding UnitId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                      SelectedValuePath="Id" />
            </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.