【问题标题】:wpf datagrid combo box binding to databasewpf datagrid组合框绑定到数据库
【发布时间】:2022-10-07 20:49:37
【问题描述】:

我希望我的 DataGrid 有带有默认值的 ComboBox,例如我有一个流派列:

  1. 动作
  2. 剧情
  3. 喜剧

    我想要 ComboBox 显示这些数据,然后选择数据库中的项目(例如,在数据库中,流派是戏剧。

    1. 我使用 WPF Net 6 和 EF Sqlite 来管理数据库。

    2. 在数据库类中,我将流派设置为字符串。

    3. 我使用的其他列的 DataGrid 是这样的:

       <DataGridTextColumn Header="ID" Binding="{Binding Path=ID,UpdateSourceTrigger=PropertyChanged}"/>
      
    4. 在代码后面:DgTest.ItemsSource=db.Test.ToList();

【问题讨论】:

    标签: wpf datagrid datagridcomboboxcolumn


    【解决方案1】:

    我强烈建议使用MVVM pattern,因为它是 WPF 应用程序的最佳实践。它可能看起来像这样

    样板

    public abstract class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            field = value;
            OnPropertyChanged(propertyName);
        }
    }
    
    public class DelegateCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;
    
        public DelegateCommand(Action execute, Func<bool> canExecute = null)
        {
            if (execute is null)
                throw new ArgumentNullException(nameof(execute));
    
            _execute = execute;
            _canExecute = canExecute ?? DefaultCanExecute;
        }
    
        private bool DefaultCanExecute() => true;
    
        public event EventHandler CanExecuteChanged;
    
        public bool CanExecute()
        {
            return _canExecute();
        }
    
        public void Execute()
        {
            _execute();
        }
    
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    
        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute();
        }
    
        void ICommand.Execute(object parameter)
        {
            Execute();
        }
    }
    
    public class DelegateCommand<T> : ICommand
    {
        private readonly Action<T> _execute;
        private readonly Func<T, bool> _canExecute;
    
        public DelegateCommand(Action<T> execute) : this(execute, null) { }
        public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
        {
            if (execute is null)
                throw new ArgumentNullException(nameof(execute));
    
            _execute = execute;
            _canExecute = canExecute ?? DefaultCanExecute;
        }
    
        private bool DefaultCanExecute(T _) => true;
    
        public event EventHandler CanExecuteChanged;
    
        public bool CanExecute(T parameter)
        {
            return _canExecute(parameter);
        }
    
        public void Execute(T parameter)
        {
            _execute(parameter);
        }
    
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    
        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute((T)parameter);
        }
    
        void ICommand.Execute(object parameter)
        {
            Execute((T)parameter);
        }
    }
    

    模型

    public class Movie
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Genre { get; set; }
    }
    

    视图模型

    public class MoviesViewModel : ObservableObject
    {
        private readonly MyDbContext _dbContext;
    
        private Task _runningQuery;
        private CancellationTokenSource _runningQueryCancellation;
        private const int _pageSize = 100;
    
        public MoviesViewModel()
        {
            _dbContext = new MyDbContext();
    
            _dbContext.Movies.Add(new Movie { Name = "foo", Genre = "Genre1" });
            _dbContext.Movies.Add(new Movie { Name = "bar", Genre = "Genre1" });
            _dbContext.Movies.Add(new Movie { Name = "baz", Genre = "Genre2" });
            _dbContext.SaveChanges();
    
            ReloadCommand = new DelegateCommand(ReloadMovies);
            NextPageCommand = new DelegateCommand(() => Page++, () => ((Page + 1) * _pageSize) < _movieCount);
            PreviousPageCommand = new DelegateCommand(() => Page--, () => Page > 0);
            _runningQuery = LoadGenresAndCount();
        }
    
        private async Task LoadGenresAndCount()
        {
            try
            {
                IsBusy = true;
                var genres = await _dbContext.Movies.Select(m => m.Genre).Distinct().OrderBy(g => g).ToListAsync();
                genres.Insert(0, null);   // add option for no Genre selected
                Genres = genres;
                MovieCount = await _dbContext.Movies.CountAsync();
                ReloadMovies();
            }
            catch (Exception ex)
            {
                Error = $"Error while loading {ex}";
            }
        }
    
        public DelegateCommand ReloadCommand { get; }
        public DelegateCommand NextPageCommand { get; }
        public DelegateCommand PreviousPageCommand { get; }
    
        // properties use SetValue to tell the view to update itself when the value changes
        private bool _isBusy;
        public bool IsBusy { get { return _isBusy; } set { SetValue(ref _isBusy, value); } }
    
        private string _error;
        public string Error { get { return _error; } private set { SetValue(ref _error, value); } }
    
        private IEnumerable<string> _genres;
        public IEnumerable<string> Genres { get => _genres; private set => SetValue(ref _genres, value); }
    
        private string _selectedGenre;
        public string SelectedGenre
        {
            get { return _selectedGenre; }
            set { SetValue(ref _selectedGenre, value); ReloadMovies(); }
        }
    
        private int _movieCount;
        public int MovieCount { get => _movieCount; private set => SetValue(ref _movieCount, value); }
    
        private IEnumerable<Movie> _movies;
        public IEnumerable<Movie> Movies { get => _movies; private set => SetValue(ref _movies, value); }
    
        private int _page;
        public int Page
        {
            get { return _page; }
            private set
            {
                SetValue(ref _page, value);
                NextPageCommand.RaiseCanExecuteChanged();
                PreviousPageCommand.RaiseCanExecuteChanged();
                ReloadMovies();
            }
        }
    
        private void ReloadMovies()
        {
            IsBusy = true;
            Error = null;
            // cancel the running query because the filters have changed
            _runningQueryCancellation?.Cancel();
            _runningQueryCancellation = new CancellationTokenSource();
            
            // having selectedGenre as parameter so it doesn't change for the asynchron operation
            _runningQuery = ReloadMoviesAsync(_runningQuery, SelectedGenre, _runningQueryCancellation.Token);
        }
        private async Task ReloadMoviesAsync(Task queryBefore, string selectedGenre, CancellationToken token)
        {
            // wait for running query to finish to prevent parallel access to the context which is not thread safe
            if (queryBefore != null)
                await queryBefore;
            try
            {
                IQueryable<Movie> query = _dbContext.Movies;
                if (selectedGenre != null)
                {
                    query = query.Where(m => m.Genre == selectedGenre);
                }
                Movies = await query
                    .OrderBy(m => m.Name)                           // usefull and nessesary for skip take
                    .Skip(_page * _pageSize).Take(_pageSize)        // only load items of the page
                    .AsNoTracking()                                 // tell ef to not track changes
                    .ToListAsync(token);
            }
            catch (Exception ex)
            {
                Error = $"Error while loading {ex}";
            }
            IsBusy = false;
        }
    }
    

    看法

    <Window x:Class="SomeApplication.MoviesView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:SomeApplication"
            mc:Ignorable="d"
            Title="Movies" Height="450" Width="800">
        <FrameworkElement.DataContext>
            <local:MoviesViewModel/>
        </FrameworkElement.DataContext>
        <FrameworkElement.Resources>
            <BooleanToVisibilityConverter x:Key="TrueToVisible"/>
        </FrameworkElement.Resources>
        <DockPanel>
            <!--header-->
            <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
                <Button Content="Reload" Command="{Binding ReloadCommand}"/>
                <ComboBox ItemsSource="{Binding Genres}" SelectedItem="{Binding SelectedGenre}"/>
            </StackPanel>
            <!--footer-->
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Bottom">
                <TextBlock Text="Page"/>
                <TextBlock Text="{Binding Page}"/>
                <TextBlock Text="Total Movies" Margin="10,0,0,0"/>
                <TextBlock Text="{Binding MovieCount}"/>
                <Button Command="{Binding PreviousPageCommand}" Content="&lt;" Margin="5"/>
                <Button Command="{Binding NextPageCommand}" Content="&gt;" Margin="5"/>
            
                <!--busy Indicator-->
                <TextBlock Text="Loading..." Visibility="{Binding IsBusy, Converter={StaticResource TrueToVisible}}"/>
            </StackPanel>
            <DataGrid ItemsSource="{Binding Movies}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" CanUserAddRows="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
                    <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
                    <DataGridComboBoxColumn Header="Genre" SelectedItemBinding="{Binding Genre}" ItemsSource="{Binding DataContext.Genres, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                </DataGrid.Columns>
                <DataGrid.Style>
                    <Style TargetType="DataGrid">
                        <Setter Property="Visibility" Value="Collapsed"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Error}" Value="{x:Null}">
                                <Setter Property="Visibility" Value="Visible"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </DataGrid.Style>
            </DataGrid>
            <TextBlock Text="{Binding Error}" HorizontalAlignment="Center" VerticalAlignment="Center">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Error}" Value="{x:Null}">
                                <Setter Property="Visibility" Value="Collapsed"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </DockPanel>
    </Window>
    

    【讨论】:

    • 谢谢你。我最近知道 mvvm 并尝试学习它。我会测试它。但我只是想让你知道我想要数据网格中的组合框而不是它之外。我想在里面使用带有组合框的 datagridcombobox 或 datagridtemplate 。所以我可以轻松地更改数据网格中的数据(使用 roweditending)。所以这个代码是我想要的吗?
    • @Saman 添加了列的绑定
    • 我正在尝试测试。那些代码。在 MyDbContext 中。我找不到代码?所以MVVM方式和代码背后的方式没有区别吗?我知道什么是 MyDbContext。只是想知道在 MVVM 方式中和在 Code Behind Way 中是否不同。如果不同,您也可以提供该代码吗?
    • 该代码使用 Datacontext,这是 WPF 中的预期方式。它应该与设置数据网格集合背后的代码相同。但是,仅当具有可用流派的对象是数据网格的数据上下文时,相对源才有效
    【解决方案2】:

    这就是我解决问题的方法。与 Firo 相同,但方式简单。

    我的类型模型:

    public int ID {get; set;}
    public string? Title {get; set;}
    

    我的存储库:

    Create A list of Genre and then Return it
    

    我的视图模型

    public ObservableCollection<Genre> AllGenreList => new (_repository.GetAllGenre())
    private Genre _genre;
    public Genre SelectedGenre{get=> _genre; set{ _genre=value; ONotifyPropertyChanged(nameof(Genre);}
    

    然后将 AllGenreList 绑定到 ComboBox。

    <ComboBox ItemsSource="{Binding AllGenreList }" SelectedItem="{Binding SelectedGenre}"/>
    

    这就是我解决问题的方法。希望解决其他问题。

    【讨论】:

      猜你喜欢
      • 2018-01-17
      • 2016-05-02
      • 2016-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多