【问题标题】:Remove DataGrid Row Clicking Image删除 DataGrid 行单击图像
【发布时间】:2020-05-13 14:15:47
【问题描述】:

尝试创建一个 Datagrid 并让用户能够通过单击图像来删除一行。生成的窗口示例:

但是,我不知道如何将单击图像与其旁边生成的单元格连接起来,该单元格由文本文件读入。每当我调用该单元格的值时,我可以看到 List,但看不到它的内容。

DataGrid XAML 代码:

        <!-- Main Shared Drive Data Grid -->

        <DataGrid HorizontalAlignment="Left" 
                  Height="309" 
                  VerticalAlignment="Top" 
                  Width="550" 
                  Margin="24,50,0,0" 
                  Name="SDDataGrid"
                  Background="Black"
                  BorderBrush="#26534e"
                  BorderThickness="4"
                  Loaded="DataGrid_Loaded"
                  AutoGenerateColumns="True"
                  IsReadOnly="True"
                  RowHeaderWidth="0"
                  HeadersVisibility="Column"
                  ColumnWidth="*">
            <DataGrid.Resources>
                <Style TargetType="{x:Type DataGridColumnHeader}">
                    <Setter Property="Background" Value="Black"/>
                    <Setter Property="FontWeight" Value="SemiBold"/>
                    <Setter Property="Foreground" Value="#459289"/>
                    <Setter Property="FontSize" Value="16"/>
                    <Setter Property="BorderThickness" Value="0,0,2,2"/>
                    <Setter Property="BorderBrush" Value="#26534e"/>
                    <Setter Property="Padding" Value="4"/>
                    <Setter Property="Width" Value="Auto"/>
                </Style>
                <Style TargetType="{x:Type DataGridRow}">
                    <Setter Property="Background" Value="Black"/>
                    <Setter Property="FontWeight" Value="SemiBold"/>
                    <Setter Property="Foreground" Value="#459289"/>
                    <Setter Property="FontSize" Value="16"/>
                    <Setter Property="BorderThickness" Value="1,1,0,2"/>
                    <Setter Property="BorderBrush" Value="#26534e"/>
                    <Setter Property="Padding" Value="4"/>
                    <Setter Property="Width" Value="Auto"/>
                </Style>
                <Style TargetType="{x:Type DataGridCell}">
                    <Setter Property="BorderThickness" Value="0,0,2,0"/>
                    <Setter Property="BorderBrush" Value="#26534e"/>
                    <Setter Property="Background" Value="Black"/>
                    <EventSetter Event="MouseDoubleClick" Handler="Do_Row_DoubleClick"/>
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTemplateColumn Width="58">
                    <DataGridTemplateColumn.HeaderTemplate>
                        <DataTemplate>
                            <TextBlock Text="Delete" Width="57"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.HeaderTemplate>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal" Width="57">
                                <Button x:Name="deleteBtn" Width="53" Click="deleteBtn_Click">
                                    <Button.Template>
                                        <ControlTemplate>
                                            <Image Source="Assets/trash.png"
                                                   Stretch="None"/>
                                        </ControlTemplate>
                                    </Button.Template>
                                </Button>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

C#代码:

    // Shared Drive List
    List<Drives> _list;

    // Build The Drive List Object
    public class Drives
    {
        public string Filepath { get; set; }

        public Drives(string line)
        {
            string[] parts = line.Split(',');
            this.Filepath = parts[0];
        }
        public string GetLine()
        {
            return this.Filepath.ToString();
        }
    }

    // Loads DataGrid Of Window With Drive List
    private void DataGrid_Loaded(object sender, RoutedEventArgs e)
    {
        var drives = new List<Drives>();

        using (StreamReader reader = new StreamReader(@"..\..\Data\SDrives.txt"))
        {
            while (true)
            {
                string line = reader.ReadLine();
                if (line == null)
                {
                    break;
                }
                drives.Add(new Drives(line));
            }
        }
        this._list = drives;
        var grid = SDDataGrid;
        grid.ItemsSource = drives;
    }

    // Adds User Submitted Filepath To Drive List And Reloads Window
    private void Add_Btn_Click(object sender, RoutedEventArgs e)
    {
        var drives = new List<Drives>();

        using (StreamWriter writer = new StreamWriter(@"..\..\Data\SDrives.txt", append: true))
        {
            writer.WriteLine(FilepathTextBox.Text);
        }

        using (StreamReader reader = new StreamReader(@"..\..\Data\SDrives.txt"))
        {
            while (true)
            {
                string line = reader.ReadLine();
                if (line == null)
                {
                    break;
                }
                drives.Add(new Drives(line));
            }
        }
        this._list = drives;
        var grid = SDDataGrid;
        grid.ItemsSource = drives;
    }

    // Launches Filepath When User Double Clicks
    private void Do_Row_DoubleClick(object sender, MouseButtonEventArgs e)
    {
        var cellInfo = SDDataGrid.CurrentCell;
        {
            var column = cellInfo.Column as DataGridBoundColumn;
            if (column != null)
            {
                var element = new FrameworkElement() { DataContext = cellInfo.Item };
                BindingOperations.SetBinding(element, TagProperty, column.Binding);
                var cellValue = element.Tag;
                if (Directory.Exists(@"" + cellValue))
                {
                    Process.Start(@"" + cellValue);
                }
                else
                {
                    System.Windows.MessageBox.Show(@"" + cellValue + " is not a valid filepath.");
                }
            }
        }
    }

    private void deleteBtn_Click(object sender, RoutedEventArgs e)
    {
        var selected = SDDataGrid.SelectedItem;
        Console.WriteLine(selected.ToString());
    }

文本文件:

C:\Users\Edward\Desktop\Projects
C:\Users\Edward\Desktop\School

谢谢!

【问题讨论】:

    标签: c# wpf datagrid


    【解决方案1】:

    我建议了解一下MVVM,它为您提供了 WPF 的力量。其中之一是实现ICommand 接口的命令。

    让我们假设您不想深入研究 MVVM,而是想立即获得解决方案。 我会在这里和现在展示它,它不是 MVVM,但有一些方法在 MVVM 中被广泛使用

    如果您希望 DataGrid 在更改集合时更新其源,只需使用 ObservableCollection 而不是 List 并在重新分配数据属性时触发 PropertyChanged 事件并自动使 DataGrid 保持最新.

    我们走吧:

    1) 为Window 实现INotifyPropertyChanged 接口。

    public partial class MainWindow : Window, INotifyPropertyChanged
    

    将代码添加到Window

    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    

    2) 将Window.DataContext 设置为自身,以告诉Binding 它应该在哪里找到目标属性。

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }
    

    3) 将数据收集设为属性,将List 更改为ObservableCollection 并在set 子句中触发PropertyChanged 事件。 ObservableCollectionList 几乎一样,所以不要害怕使用它。

    private ObservableCollection<Drives> _drivesList; // backing field, never interact with it
                                                      // but use DrivesList instead
    public ObservableCollection<Drives> DrivesList
    {
        get => _drivesList; // same as get { return _drivesList; } but shorter
        set
        {
            _drivesList = value;
            OnPropertyChanged();
        }
    }
    

    4) 并在 xaml 中将 DataGrid 绑定到它。

    <DataGrid ItemsSource="{Binding DrivesList}"
              ...>
    

    5) 到此,我们完成了动态DataGrid 更新。结果我们有一些代码冗余,让我们进行清理。

    private void DataGrid_Loaded(object sender, RoutedEventArgs e)
    {
        DrivesList = new ObservableCollection<Drives>();
        using (StreamReader reader = new StreamReader(@"..\..\Data\SDrives.txt"))
        {
            while (!reader.EndOfStream)
            {
                string line = reader.ReadLine();
                DrivesList.Add(new Drives(line));
            }
        }
    }
    
    private void Add_Btn_Click(object sender, RoutedEventArgs e)
    {
        string path = FilepathTextBox.Text;
        using (StreamWriter writer = new StreamWriter(@"..\..\Data\SDrives.txt", append: true))
        {
            writer.WriteLine(path);
        }
        DrivesList.Add(new Drives(path));
    }
    

    问题的答案:

    6) 但是你会遇到删除问题,因为当你点击 Delete 按钮时,它会删除SelectedItem,但不会删除按钮所在的行。唯一的解决方案是将按钮所在的行传递给删除方法。最友好的解决方案是 Command。

    6.1) 这里是方便命令使用的现成辅助类。把它放到Window类之外的项目中:在Solution Explerer中右击项目,选择Add => Class => RelayCommand.cs 。并在那里添加以下代码。 添加一次,根据需要多次使用。

    命名空间

    using System;
    using System.Windows.Input;
    

    班级

    public class RelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;
    
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    
        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _execute = execute;
            _canExecute = canExecute;
        }
    
        public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
        public void Execute(object parameter) => _execute(parameter);
    }
    

    6.2)Window类中命令的实现

    private ICommand _deleteCommand;
    
    public ICommand DeleteCommand => _deleteCommand ?? (_deleteCommand = new RelayCommand(parameter =>
    {
        if (parameter is Drives drives)
        {
            DrivesList.Remove(drives);
            using (StreamWriter writer = new StreamWriter(@"..\..\Data\SDrives.txt", append: false))
            {
                foreach(Drives drives in DrivesList)
                {
                    writer.WriteLine(drives.Filepath);
                }
            }
        }
    }));
    

    6.3) 以及在xaml中的使用

    <Button Width="53"
            Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=Window}}"
            CommandParameter="{Binding}">
        <Button.Template>
            <ControlTemplate>
                <Image Source="Assets/trash.png"
                       Stretch="None"/>
            </ControlTemplate>
        </Button.Template>
    </Button>
    

    奖金部分

    7) 你也可以用命令替换Click 事件。

    例如你有Add按钮

    <TextBox x:Name="FilepathTextBox"/>
    
    <Button Content="Add" Click="Add_Btn_Click"/>
    

    替换为

    <TextBox x:Name="FilepathTextBox"/>
    
    <Button Content="Add"
            Command="{Binding AddCommand}"
            CommandParameter="{Binding Text,ElementName=FilepathTextBox}"/>
    

    并在Window 类中实现它

    private ICommand _addCommand;
    
    public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(parameter =>
    {
        if (parameter is string path)
        {
            using (StreamWriter writer = new StreamWriter(@"..\..\Data\SDrives.txt", append: true))
            {
                writer.WriteLine(path);
            }
            DrivesList.Add(new Drives(path));
        }
    }));
    

    一般而言,在Button 情况下,命令看起来并不比事件处理程序简单,但它具有不同的功能。例如禁用条件为CanExecute 的按钮。您可以尝试在CanExecute 中返回false,然后按钮将被禁用。如DataGrid 所示,您还可以通过CommandParameter 传递任何对象。

    主要特点是:命令可以位于您可以设置窗口的DataContext 的任何位置,但事件处理程序只能位于Window 类中。 (MVVM 中的命令和属性位于单独的 ViewModel 类中)。

    public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(parameter =>
    {
        string path = (string)parameter;
        using (StreamWriter writer = new StreamWriter(@"..\..\Data\SDrives.txt", append: true))
        {
            writer.WriteLine(path);
        }
        DrivesList.Add(new Drives(path));
    },
    parameter => parameter is string path && path.Length > 0));
                 // Here's CanExecute, any condition here may be used.
                 // This condition will prevent adding empty lines
                 // and will disable the Button if TextBox is empty, automatically
    

    【讨论】:

    • 该解决方案使用 ObservableCollection 进行加载和添加效果很好,但是它只会从 DatGrid 中删除行,而不是从它所引用的文本文件中删除,因此当窗口重新加载时,该行会重新出现。现在玩这个来尝试解决。谢谢您的帮助!我需要研究 MVVM,因为这将是我正在开发的一个相当大的应用程序。
    • @Sanavi 您可能会在删除时覆盖整个文件内容。如果有帮助,请不要将答案标记为已接受。
    • 抱歉,已接受答案,第一次提出问题,呵呵——现在只是想弄清楚如何覆盖文件。
    • @Sanavi 我在答案中添加了对DeleteCommand 的改进。
    猜你喜欢
    • 2011-04-29
    • 2023-03-15
    • 1970-01-01
    • 2021-09-23
    • 2013-01-20
    • 1970-01-01
    • 1970-01-01
    • 2010-12-20
    • 1970-01-01
    相关资源
    最近更新 更多