WPF c#数据网格组合框的新行不更新数据源(已更新!)

本文关键字:数据源 更新 已更新 新行不 数据 数据网 网格 组合 WPF | 更新日期: 2023-09-27 18:16:04

这是一个展示我遇到麻烦的行为的例子。我有一个数据网格,它被绑定到一个视图模型记录的可观察集合。在数据网格中,我有一个datagridtemplatecolum列,它包含一个从视图模型中的列表填充的组合框。数据网格还包含文本列。在窗口的底部有一些文本框显示记录的内容。

<Window x:Class="Customer.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Customer"
    Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:SelectedRowConverter x:Key="selectedRowConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="8*"/>
            <RowDefinition Height="3*"/>
        </Grid.RowDefinitions>
        <DataGrid x:Name="dgCustomers" AutoGenerateColumns="False"
                  ItemsSource="{Binding customers}" SelectedItem="{Binding SelectedRow,
                    Converter={StaticResource selectedRowConverter}, Mode=TwoWay}"
                  CanUserAddRows="True" Grid.Row="0" SelectionChanged="dgCustomers_SelectionChanged">
            <DataGrid.Columns>
                <DataGridTemplateColumn Width="Auto" Header="Country">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox x:Name="cmbCountry" ItemsSource="{Binding DataContext.countries,
                                RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                      DisplayMemberPath="name" SelectedValuePath="name" Margin="5"
                                      SelectedItem="{Binding DataContext.SelectedCountry,
                                RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=TwoWay,
                                UpdateSourceTrigger=PropertyChanged}" SelectionChanged="cmbCountry_SelectionChanged" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="Name" Binding="{Binding name}" Width="1*"/>
                <DataGridTextColumn Header="Phone" Binding="{Binding phone}" Width="1*"/>
            </DataGrid.Columns>
        </DataGrid>
        <Grid x:Name="grdDisplay" DataContext="{Binding ElementName=dgCustomers}" Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="2" Content="Country:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
            <Label Grid.Column="4" Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
            <BulletDecorator  Grid.Column="0">
                <BulletDecorator.Bullet>
                    <Label Content="Name:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
                </BulletDecorator.Bullet>
                <TextBox x:Name="txtId" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.name}" Margin="5,5,5,5"/>
            </BulletDecorator>
            <BulletDecorator Grid.Column="1">
                <BulletDecorator.Bullet>
                    <Label Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
                </BulletDecorator.Bullet>
                <TextBox x:Name="txtCode" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.countryCode}" Margin="5,5,5,5"/>
            </BulletDecorator>
            <BulletDecorator Grid.Column="2">
                <BulletDecorator.Bullet>
                    <Label  Content="Phone:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
                </BulletDecorator.Bullet>
                <TextBox x:Name="txtPhone" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.phone}" Margin="5,5,5,5"/>
            </BulletDecorator>
        </Grid>
    </Grid>
</Window>

最初没有记录,所以数据网格是空的,只显示包含组合框的一行。如果用户首先在文本列中输入数据,则将一条记录添加到集合中,并且可以将组合框值添加到记录中。但是,如果用户首先选择了组合框值,那么当选择另一列时,该值将消失。如何将组合框数据添加到记录中,如果它首先被选中?

后台代码:

public partial class MainWindow : Window
{
    public GridModel gridModel { get; set; }
    public MainWindow()
    {
        InitializeComponent();
        gridModel = new GridModel();
        //dgCustomers.DataContext = gridModel;
        this.DataContext = gridModel;
    }
    private void cmbCountry_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ComboBox c = sender as ComboBox;
        Debug.Print("ComboBox selection changed, index is " + c.SelectedIndex + ", selected item is " + c.SelectedItem);
    }
}

Record类:

public class Record : ViewModelBase
{
    private string _name;
    public string name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged("name");
        }
    }
    private string _phone;
    public string phone
    {
        get { return _phone; }
        set
        {
            _phone = value;
            OnPropertyChanged("phone");
        }
    }
    private int _countryCode;
    public int countryCode
    {
        get { return _countryCode; }
        set
        {
            _countryCode = value;
            OnPropertyChanged("countryCode");
        }
    }
}

国家类:

public class Country : ViewModelBase
{
    private string _name;
    public string name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged("name");
        }
    }
    private int _id;
    public int id
    {
        get { return _id; }
        set
        {
            _id = value;
            OnPropertyChanged("id");
        }
    }
    private int _code;
    public int code
    {
        get { return _code; }
        set
        {
            _code = value;
            OnPropertyChanged("code");
        }
    }
    public override string ToString()
    {
        return _name;
    }
}

GridModel:

public class GridModel : ViewModelBase
{
    public ObservableCollection<Record> customers { get; set; }
    public List<Country> countries { get; set; }
    public GridModel()
    {
        customers = new ObservableCollection<Record>();
        countries = new List<Country> { new Country { id = 1, name = "England", code = 44 }, new Country { id = 2, name = "Germany", code = 49 },
        new Country { id = 3, name = "US", code = 1}, new Country { id = 4, name = "Canada", code = 11 }};
    }
    private Country _selectedCountry;
    public Country SelectedCountry
    {
        get
        {
            return _selectedCountry;
        }
        set
        {
            _selectedCountry = value;
            _selectedRow.countryCode = _selectedCountry.code;
            OnPropertyChanged("SelectedRow");
        }
    }
    private Record _selectedRow;
    public Record SelectedRow
    {
        get
        {
            return _selectedRow;
        }
        set
        {
            _selectedRow = value;
            Debug.Print("Datagrid selection changed"); 
            OnPropertyChanged("SelectedRow");
        }
    }
}

转换器:

class Converters
{
}
public class SelectedRowConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Record)
            return value;
        return new Customer.Record();
    }
}

ViewModelBase:

public class ViewModelBase : INotifyPropertyChanged
{
    public ViewModelBase()
    {
    }
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
}

WPF c#数据网格组合框的新行不更新数据源(已更新!)

您所看到的行为是预期的。其背后的原因是ComboBox ItemsSourceSelectedItem都绑定在WindowDataContextProperties上,而其他列都绑定在DataGridItemsSource上。因此,当您修改除下拉列表之外的列时,数据将被添加到可观察对象集合中。

你能做的是从下拉菜单中选择一个值后,你需要自己添加一个记录(可能通过调用SelectedCountry属性的函数)

编辑

基于你的代码,我做了一个工作模型,尽可能少地改变你现有的代码。我不能使用转换器,因为我没有类Customer

的详细信息

Xaml

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="8*"/>
        <RowDefinition Height="3*"/>
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0">
        <Button HorizontalAlignment="Right" Content="Add User" Margin="0,2,2,2" Command="{Binding AddUserCommand}"/>
        <DataGrid x:Name="dgCustomers"
          AutoGenerateColumns="False"
          ItemsSource="{Binding customers}"
          SelectedItem="{Binding SelectedRow}"
          SelectionUnit="FullRow"
          CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn Width="Auto" Header="Country">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox Focusable="False" 
                                      ItemsSource="{Binding DataContext.countries, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
                                      DisplayMemberPath="name"
                                      SelectedValuePath="code"
                                      SelectedValue="{Binding countryCode, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="Name" Binding="{Binding name, UpdateSourceTrigger=PropertyChanged}" Width="1*"/>
                <DataGridTextColumn Header="Phone" Binding="{Binding phone, UpdateSourceTrigger=PropertyChanged}" Width="1*"/>
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
    <Grid x:Name="grdDisplay" DataContext="{Binding ElementName=dgCustomers}" Grid.Row="1">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Column="2" Content="Country:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
        <Label Grid.Column="4" Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
        <BulletDecorator  Grid.Column="0">
            <BulletDecorator.Bullet>
                <Label Content="Name:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
            </BulletDecorator.Bullet>
            <TextBox x:Name="txtId" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.name}" Margin="5,5,5,5"/>
        </BulletDecorator>
        <BulletDecorator Grid.Column="1">
            <BulletDecorator.Bullet>
                <Label Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
            </BulletDecorator.Bullet>
            <TextBox x:Name="txtCode" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.countryCode}" Margin="5,5,5,5"/>
        </BulletDecorator>
        <BulletDecorator Grid.Column="2">
            <BulletDecorator.Bullet>
                <Label  Content="Phone:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
            </BulletDecorator.Bullet>
            <TextBox x:Name="txtPhone" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.phone}" Margin="5,5,5,5"/>
        </BulletDecorator>
    </Grid>
</Grid>

你的GridModel类

public class GridModel : ViewModelBase
{
    public ObservableCollection<Record> customers { get; set; }
    public ObservableCollection<Country> countries
    {
        get;
        private set;
    }
    public GridModel()
    {
        customers = new ObservableCollection<Record> { };
        AddUserCommand = new RelayCommand(AddNewUser);
        countries = new ObservableCollection<Country> 
        { 
            new Country { id = 1, name = "England", code = 44 },
            new Country { id = 2, name = "Germany", code = 49 },
            new Country { id = 3, name = "US", code = 1},
            new Country { id = 4, name = "Canada", code = 11 }
        };
    }
    private void AddNewUser()
    {
        customers.Add(new Record());
    }
    public ICommand AddUserCommand { get; set; }
    private Record _selectedRow;
    public Record SelectedRow
    {
        get
        {
            return _selectedRow;
        }
        set
        {
            _selectedRow = value;
            Debug.Print("Datagrid selection changed");
            OnPropertyChanged("SelectedRow");
        }
    }
}

我使用了包含RelayCommand的MVVMLight工具包。您还可以定义自己的ICommand实现,并使用它来代替工具包

编辑2

修正了我引入的错误,如果数据来自数据库,将阻止组合框显示国家。改进后的代码不需要任何转换器