问题
So currently I have a DataGrid that looks like this: Current DataGridView
The goal behind this is to create an import utility. The data received in CSV format isn't always structured the same way. The columns change order, sometimes they only provide partial data.
I'd like for the user to be able to select where each column is directed. I'm running into a few problems and I was hoping someone more experienced could direct me.
First, the type of the data entered is still restricted. So for example, if column 1 is an integer, then it won't allow for text input. I was planning on writing an event handler for when the headers ComboBox changed to change the BindingExpression. but realistically this just needs to be a typeless column. Which would be entered into the actual table based on the comboBox selection afterwards.
I'm also unsure how to identify/get at the ComboBox from the ViewModel when it's generated this way.
xaml
<DataGrid x:Name="ImportTable"
ItemsSource="{Binding displayTable}"
AutoGeneratingColumn="OnAutoGeneratingColumn"
AutoGenerateColumns="True"
CanUserAddRows="True"
CanUserDeleteRows="True"
EnableColumnVirtualization="True"
EnableRowVirtualization="True"
MaxWidth="1300"
MaxHeight="600"
/>
xaml.cs
//i keeps track of the column index, temporary solution to preset columns
private int i;
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var cb = new ComboBox();
cb.ItemsSource = (DataContext as EnterValueDialogViewModel).displayTable.Columns;
cb.DisplayMemberPath = "ColumnName";
cb.SelectedValue = e.PropertyName.ToString();
cb.SelectedIndex = i;
e.Column.Header = cb;
i++;
}
回答1:
Here's a rough sketch of the basic wiring. We've got a class called ColumnHeaderViewmodel
that represents the header for one column. The MainViewModel
has a collection of those as well as a DataTable
property called Data
. That DataTable
is one relatively tolerable way to display arbitrary, known-only-at-runtime CSV in a DataGrid
. Another way is an ObservableCollection<ExpandoObject>
, with some extra code to generate the columns. We can go into that if need be.
It's a bad idea to create WPF UI in C#. Anybody who's experienced with WPF avoids that kind of thing if he possibly can.
We'll store the CSV in a separate structure, perhaps List<List<String>>
or something, and every time the column headers change, we'll repopulate the DataTable
from that collection. That's your problem, I'm not addressing it. See the "TODO" comments.
This code is a complete self-contained example that lets you rename columns from header comboboxes. You can add other stuff (e.g. datatype, or an include/exclude flag) to ColumnHeaderViewModel
and to the header template.
First, we'll illustrate how to put comboboxes in DataGrid column headers. Don't do it in C#.
<DataGrid
ItemsSource="{Binding Data}"
AutoGeneratingColumn="DataGrid_AutoGeneratingColumn"
>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox
ItemsSource="{Binding ColumnNames}"
SelectedItem="{Binding ColumnName}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
Second, in the codebehind file for the above XAML (maybe your MainWindow, maybe a UserControl), we'll wire up the ColumnHeaderViewModels; they're in ViewModel.Columns
:
public MainViewModel ViewModel => DataContext as MainViewModel;
private void DataGrid_AutoGeneratingColumn(object sender,
DataGridAutoGeneratingColumnEventArgs e)
{
e.Column.Header = ViewModel.Columns
.FirstOrDefault(c => c.ColumnName == e.PropertyName);
}
MainViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
// TODO:
// Load CSV
// TODO:
// These are fake. Get actual column names from CSV
ColumnNames = new List<string> { "Foo", "Bar", "Numeric" };
Columns = new List<ColumnHeaderViewModel>();
foreach (var name in ColumnNames)
{
Data.Columns.Add(new DataColumn(name));
var col = new ColumnHeaderViewModel(ColumnNames, name);
col.NameChanging += Column_NameChanging;
Columns.Add(col);
}
UpdateDataTableFromCSVRows();
}
private void Column_NameChanging(object sender, ValueChangingEventArgs<String> e)
{
var col = sender as ColumnHeaderViewModel;
// Swap names. DataTable throws an exception on column name collisions
var otherCol = Columns.FirstOrDefault(c => c != col && c.ColumnName == e.NewValue);
if (e.OldValue != null && otherCol != null)
{
// Use Rename() method so it won't raise NameChanged again.
// However, UpdateDataTableFromCSVRows() should be clever enough
// to do nothing in cases where the column order is unchanged.
otherCol.Rename(e.OldValue);
}
UpdateDataTableFromCSVRows();
}
protected void UpdateDataTableFromCSVRows()
{
// TODO:
// Update the DataTable from the CSV rows, based on the new
// column names.
}
public List<ColumnHeaderViewModel> Columns { get; private set; }
public List<String> ColumnNames { get; private set; }
private DataTable _data = default(DataTable);
public DataTable Data
{
get { return _data; }
private set
{
if (value != _data)
{
_data = value;
OnPropertyChanged();
}
}
}
}
ColumnHeaderViewModel
public class ValueChangingEventArgs<T> : EventArgs
{
public ValueChangingEventArgs(T oldValue, T newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
public T OldValue { get; private set; }
public T NewValue { get; private set; }
}
public class ColumnHeaderViewModel : ViewModelBase
{
public ColumnHeaderViewModel(List<String> names, string name)
{
ColumnNames = names;
ColumnName = name;
}
public List<String> ColumnNames { get; private set; }
public event EventHandler<ValueChangingEventArgs<String>> NameChanging;
#region ColumnName Property
private String _columnName = default(String);
public String ColumnName
{
get { return _columnName; }
set
{
if (value != _columnName)
{
var oldName = ColumnName;
_columnName = value;
OnPropertyChanged();
NameChanging?.Invoke(this, new ValueChangingEventArgs<string>(oldName, ColumnName));
}
}
}
#endregion ColumnName Property
// Rename without raising NameChanging
public void Rename(string newName)
{
_columnName = newName;
OnPropertyChanged(nameof(ColumnName));
}
}
来源:https://stackoverflow.com/questions/49885086/changing-column-based-on-header-combobox-selection