WPF DataGrid DataBinding is not displayed

I have a DataGrid in my current WPF Application which I would like to bind to a ViewModel that holds a ObservableCollection. The user can enter search values in some TextBoxes and after enter has been hit I am performing an query to our database that retunrs a table of records. From these records I am populate the data for the ObservableCollection. I am now struggeling now that the datagrid is not displaying the data.

I have read a howl bunch of posts about the binding but I am still missing something I think.

Product.cs

public class Product : InotifyPropertyChanged, IEditableObject
{
    public string Title { get; set; } = "";

    //public Product()
    //{

    //}
    private ProductViewModel _productViewModel = new ProductViewModel();
    public ProductViewModel productViewModel { get { return _productViewModel; } set { _productViewModel = value; } }

    public DataTable ProductsTable { get; set; }

    public void GetProducts(string filter)
    {
        //< --doing some stuff to fill the table-->

        foreach (DataRow row in ProductsTable.Rows)
        {
            productViewModel.Products.Add(new Product
            {

                Title = (string)row["TITLE"],

            });
        }
    }
}

ProductViewModel.cs

public class ProductViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private Product _SelectedProduct;
    private ObservableCollection<Product> _Products = new ObservableCollection<Product>();
    public ObservableCollection<Product> Products { get { return _Products; } set { _Products = value; } }

    public ProductViewModel()
    {
    }

    public void NotifyPropertyChanged(string propertyName)
    {

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

ProductWindow.xaml

<DataGrid 
    Name="ProductsGrid" 
    AutoGenerateColumns="False" 
    ItemsSource="{Binding Products, Mode=TwoWay, NotifyOnSourceUpdated=True}" 
    SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"  
    CanUserAddRows="False" SelectionUnit="FullRow"  
    VerticalAlignment="Stretch" 
    Grid.Row="0" 
    Margin="10,10,10,10" 
    >
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Title}" Header="Title"></DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

ProductWindow.xaml.cs

    public partial class ProductWindow : Page
{
    public object DialogResult { get; private set; }
    //public ProductViewModel ProductViewModel;
    public ProductWindow()
    {

        InitializeComponent();

        DataContext = new ProductViewModel();//stackflow
        //var ProductViewModel = products.ProductViewModel;
        //ProductsGrid.DataContext = new ProductViewModel();

    }

    public ProductViewModel ViewModel => DataContext as ProductViewModel;

    private void OnKeydownHandler(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            var tb = sender as TextBox;
            Product products = new Product();
            string filter = "";//performing some ifelse to create filter
            products.GetProducts(filter);
            //ProductsGrid.DataContext = products.ProductsTable;
            //ProductsGrid.DataContext = products.productViewModel;

        }
        else if (e.Key == Key.Escape)
        {
            ProductsGrid.DataContext = null;
            foreach (TextBox tb in FindVisualChildren<TextBox>(this))
            {
                // do something with tb here
                tb.Text = "";
            }
        }
    }

}


If DataContext is a ProductViewModel , and the Products collection of that ProductViewModel is populated, you will see rows in your DataGrid . I've tested that. It appears that the viewmodel you're giving it may not have any rows.

That said, there's a problem with your design:

Product creates a ProductViewModel . ProductViewModel creates a collection of Product . Each Product , as I just said, creates a ProductViewModel . Which creates a collection of Product . They keep creating each other until you get a StackOverflowException . If you're not seeing that, you must be calling GetProducts() from somewhere else.

But there's no need for Product to own a copy of ProductViewModel . That's like adding a car to each wheel on your car.

So let's do this instead: ProductViewModel owns a collection of Product . Just that. And we'll call GetProducts() to make sure we get some items in the grid. Your binding is fine. You just weren't populating the collection.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DataContext = new ProductViewModel();
    }

    //  Now you can call ViewModel.GetProducts(filterString) from an event handler. 
    //  It would be more "correct" to use a Command, but let's take one step at a time. 
    public ProductViewModel ViewModel => DataContext as ProductViewModel;
}

Viewmodels

//  You didn't include any implementation of IEditableObject. I presume 
//  you can add that back in to this version of the class. 
public class Product : INotifyPropertyChanged, IEditableObject
{
    //  You weren't raising PropertyChanged here, or anywhere at all. 
    //  In every setter on a viewmodel, you need to do that. 
    private string _title = "";
    public string Title {
        get => _title;
        set
        {
            if (_title != value)
            {
                _title = value;
                NotifyPropertyChanged(nameof(Title));
            }
        }
    }

    public Product()
    {
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propertyName)
    {

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class ProductViewModel : INotifyPropertyChanged
{
    public ProductViewModel()
    {
        GetProducts("");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private Product _SelectedProduct;

    public Product SelectedProduct
    {
        get { return _SelectedProduct; }
        set
        {
            if (value != _SelectedProduct)
            {
                _SelectedProduct = value;
                NotifyPropertyChanged(nameof(SelectedProduct));
            }
        }
    }


    public DataTable ProductsTable { get; set; }

    public void GetProducts(string filter)
    {
        //< --doing some stuff to fill the table-->

        Products.Clear();

        foreach (DataRow row in ProductsTable.Rows)
        {
            Products.Add(new Product
            {
                Title = (string)row["TITLE"],
            });
        }
    }

    private ObservableCollection<Product> _Products = new ObservableCollection<Product>();
    //  This setter MUST raise PropertyChanged. See the Title property above for example. 
    public ObservableCollection<Product> Products { get { return _Products; } private set { _Products = value; } }

    public void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Update

Here's the problem: You create a new Product , which creates its own ProductsViewModel . Nothing is bound to any property of that viewmodel. You fill its collection and the DataGrid doesn't know or care, because you bound its ItemsSource to a property of a different object.

So use my suggestions above, particularly the ViewModel property of the window. I just made a change in ProductsViewModel.GetProducts() that you need to copy: Now it calls Products.Clear() before populating the collection.

    if (e.Key == Key.Enter)
    {
        var tb = sender as TextBox;

        //  Don't create this
        //Product products = new Product();

        string filter = "";//performing some ifelse to create filter
        ViewModel.GetProducts(filter);

    }
    else if (e.Key == Key.Escape)
    {
        //  Setting the DataContext to null breaks everything. Never do that. 
        //ProductsGrid.DataContext = null;

        //  Instead, just clear the collection. It's an ObservableCollection so it will 
        //  notify the DataGrid that it was cleared. 
        ViewModel.Products.Clear();

        foreach (TextBox tb in FindVisualChildren<TextBox>(this))
        {
            // do something with tb here
            tb.Text = "";
        }
    }
链接地址: http://www.djcxy.com/p/75244.html

上一篇: LINQ to Entities Union正在抛出一个错误

下一篇: 不显示WPF DataGrid DataBinding