【问题标题】:WPF C# datagrid combobox on new row won't update datasource (updated!)新行上的 WPF C# 数据网格组合框不会更新数据源(已更新!)
【发布时间】:2015-12-31 18:42:40
【问题描述】:

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

<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>

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

代码隐藏:

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);
    }
}

记录类:

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;
    }
}

网格模型:

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));
        }
    }
}

【问题讨论】:

    标签: c# wpf mvvm combobox datagrid


    【解决方案1】:

    您看到的行为符合预期。其背后的原因是ComboBoxItemsSourceSelectedItem 都绑定到WindowDataContextProperties,而其他列绑定到您的DataGrid 的@ 987654329@。因此,当您修改下拉列表以外的列时,数据将添加到可观察集合中。

    您可以做的是,从下拉列表中选择一个值后,您需要自己添加一条记录(可能通过从您的 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");
            }
        }
    }
    

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

    编辑 2

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

    【讨论】:

    • 您好 Sandesh,非常感谢您的回答!因此,当调用 SelectedCountry 时,我使用该属性来更改 SelectedRow:code set { _selectedCountry = value; _selectedRow.countryCode = _selectedCountry.code; OnPropertyChanged("SelectedRow"); } code 我认为使用双向绑定会更新数据网格?
    • 如果数据网格显示的记录在其 ItemsSource 中不存在,人们会觉得这很令人毛骨悚然:)
    • 如果它可以猜到你想要的东西,会节省很多打字时间:) 我必须明确地将 SelectedRow 添加到 ObservableCollection 中,但前提是它不存在......所以我需要知道从哪一行调用了 SelectedCountry,并且只有在 ObservableCollection 不包含那么多行时才添加它。关于如何从 SelectedCountry 属性中获取数据网格行索引的任何想法?
    • 一般 DataGrid 知道哪一行被修改。如果您提供设计的详细信息,我可能会提供帮助
    • 如果您确切知道自己在做什么,WPF 很容易
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-24
    • 1970-01-01
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 2012-10-13
    相关资源
    最近更新 更多