Search code examples
c#.netwpfdatagrid

WPF DataGrid filename column allowing direct text input or button to browse via dialog


I have a WPF app with a DataGrid where the user will enter filenames. I want them to either be able to type in the filenames directly by double clicking, or click a Browse button to launch an open file dialog.

Here is the XAML:

<Window x:Class="WpfApp1.MainWindow"
        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"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <DataGrid Name="TheDataGrid" 
              ItemsSource="{Binding Inputs}" 
              AutoGenerateColumns="False" 
              CanUserAddRows="True">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Filename" Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <DockPanel>
                            <Button DockPanel.Dock="Right" Content="Browse" Click="BrowseButton_Click" />
                            <TextBlock Text="{Binding Filename}" />
                        </DockPanel>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <DockPanel>
                            <Button DockPanel.Dock="Right" Content="Browse" Click="BrowseButton_Click" />
                            <TextBox Text="{Binding Filename}" />
                        </DockPanel>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Window>

and here is the code:

public partial class MainWindow : Window
{
    public ObservableCollection<Input> Inputs { get; set; } = new ObservableCollection<Input>();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog ofd = new OpenFileDialog();
        if (!ofd.ShowDialog().Value)
            return;
        if (((Button)sender).DataContext is Input i)
            i.Filename = ofd.FileName;
        else
        {
            // what to do here?
        }
    }
}

public class Input : INotifyPropertyChanged
{
    private string filename;

    public string Filename
    {
        get { return filename; }
        set
        {
            filename = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Filename"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

My issues are:

  1. When the user clicks the Browse button on a new row, the DataContext is not type Input, but rather DataGrid.NewItemPlaceholder, so I have nothing to set the filename to. I tried instead modifying the TextBlock Text directly, which works, but then it doesn't commit the new row, even when issuing that command manually.
  2. When the user double-clicks to type the filename manually, it enters edit mode but then seems to require one more click to actually accept the typing. Is there any way to avoid this, and make the behavior more like a DataGridTextColumn?

Solution

  • Coming back to this after a day, and looking at EldHasp's answer (and realizing the hard part is adding a new row), the solution seems painfully obvious now:

    private void BrowseButton_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog ofd = new OpenFileDialog();
        if (!ofd.ShowDialog().Value)
            return;
        if (!(((Button)sender).DataContext is Input i))
            Inputs.Add(new Input() { Filename = ofd.FileName });
        else
            i.Filename = ofd.FileName;
    }
    

    When the DataContext is not an Input, i.e. it's a new row, you simply create a new one (setting the appropriate property) and add it to the bound collection.