WPF C# Get Cell value from autogenerated Data Grid and Data Table - c#

I have created simple mechanism for data table xaml would looks simply:
<DataGrid ItemsSource="{Binding CurrentsFlagValuesView}" AutoGenerateColumns="True" />
And MVVM code behind is based on data tables, and as well fairly simple:
private void GenerateDataView()
{
CurrentsFlagValuesView = new DataTable();
CurrentsFlagValuesView.Columns.Add("Bits");
var bitLength = 0;
foreach (CurrentsFlagAnalysis flag in CurrentsFlagValues)
{
CurrentsFlagValuesView.Columns.Add(flag.DailyCurrentsTimestampInterval.ToString("yyyy-MM-dd"));
bitLength = flag.CurrentFlagsLength;
}
for (var bit = 0; bit < bitLength; bit++)
{
List<CurrentFlagEventEnum> flags = CurrentsFlagValues
.Select(value => value.CurrentFlags.ElementAt(bit))
.Select(value => value ? (CurrentFlagEventEnum)bit + 1 : CurrentFlagEventEnum.None)
.ToList();
var dataRowValues = new List<object> { bit };
dataRowValues.AddRange(flags.Cast<object>());
CurrentsFlagValuesView.Rows.Add(dataRowValues.ToArray());
}
}
But I came upon a problem, or two I want to get data of the cell when I click the cell, like Column title, and value of the cell. I managed to do this without MVVM like:
void EditingDataGrid_CurrentCellChanged(object sender, EventArgs e)
{
DataGridCell Cell = EditingDataGrid.GetCurrentDataGridCell();
var Position = Cell.PointToScreen(new Point(0, 0));
TextBlock text = (TextBlock)Cell.Content;
MessageBox.Show("Value=" + text.Text, "Position" );
}
public static DataGridCell GetCurrentDataGridCell(this DataGrid dataGrid)
{
DataGridCellInfo cellInfo = dataGrid.CurrentCell;
if (cellInfo.IsValid == false)
{
return null;
}
var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
if (cellContent == null)
{
return null;
}
return cellContent.Parent as DataGridCell;
}
But now I want to remodel that to that pattern, but I do not know how. Any Ideas how to bind command to that?

You could bind the CurrentCell property of the DataGrid to a DataGridCellInfo (not DataGridCell) source property provided that you set the Mode of the Binding two TwoWay:
<DataGrid ItemsSource="{Binding CurrentsFlagValuesView}"
CurrentCell="{Binding CurrentCell, Mode=TwoWay}"
AutoGenerateColumns="True" />
Then the source property of the view model will be set whenever you select a cell in the view and you could simply move your current logic to the view model:
private DataGridCellInfo _currentCell;
public DataGridCellInfo CurrentCell
{
get { return _currentCell; }
set { _currentCell = value; OnCurrentCellChanged(); }
}
void OnCurrentCellChanged()
{
DataGridCell Cell = GetCurrentDataGridCell(_currentCell);
var Position = Cell.PointToScreen(new Point(0, 0));
TextBlock text = (TextBlock)Cell.Content;
MessageBox.Show("Value=" + text.Text, "Position");
}
public static DataGridCell GetCurrentDataGridCell(DataGridCellInfo cellInfo)
{
if (cellInfo == null || cellInfo.IsValid == false)
{
return null;
}
var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
if (cellContent == null)
{
return null;
}
return cellContent.Parent as DataGridCell;
}
You could also wrap this functionality in a behaviour that sets the source property of the view model to the actuall cell value:
https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF
https://blog.magnusmontin.net/2014/01/30/wpf-using-behaviours-to-bind-to-readonly-properties-in-mvvm/

You can simply bind the current cell property in the view model and you will have the current cell with you all the time:
<DataGrid AutoGenerateColumns="True"
SelectionUnit="Cell"
CanUserDeleteRows="True"
ItemsSource="{Binding Results}"
CurrentCell="{Binding CellInfo}"
SelectionMode="Single">
In the view model:
private DataGridCell cellInfo;
public DataGridCell CellInfo
{
get { return cellInfo; }
}

Related

How to get the underlying (TextBox) Control of a DataGridTemplateColumn

I use this code to add a TextBox to a DataGrid cell: (no, I can't use XAML here)
Binding binding = new Binding("Fld_Company");
binding.Mode = BindingMode.OneWay;
FrameworkElementFactory frameworkElementFactory = new FrameworkElementFactory(typeof(TextBox));
DataTemplate dataTemplate = new DataTemplate();
dataTemplate.VisualTree = frameworkElementFactory;
frameworkElementFactory.SetBinding(TextBox.TextProperty, binding);
DataGridTemplateColumn dataGridTemplateColumn = new DataGridTemplateColumn();
dataGridTemplateColumn.IsReadOnly = true;
dataGridTemplateColumn.Header = "Company";
dataGridTemplateColumn.CellTemplate = dataTemplate;
this.dataGrid.Columns.Add(dataGridTemplateColumn);
I there a way to get the underlying TextBox control without XAML?
What I tried:
VisualTreeHelper, but the GetChildrenCount() is always 0
FindName, but I haven't found a proper FrameworkElement
After explored the DataGrid for a while I see that my question does not make any sense. My code above prepares just the DataGrid but does not fill any data. Until that no rows are generated and therefore no underlying TextBox controls can be found.
When the DataGrid gets finally filled with data, the best way to get the underlying controls seems to be catching the LoadinRow event. But when this event fires, the loading of the row is not finished. There needs to be temporarily assigned a second event which fires when the row is finally loaded.
{
DataGrid.LoadingRow += DataGrid_LoadingRow;
}
private void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
// The visual tree is not built until the row is "loaded". This event fires when this happend:
e.Row.Loaded += DataGrid_Row_Loaded;
}
private void DataGrid_Row_Loaded(object sender, RoutedEventArgs e)
{
DataGridRow dataGridRow = (DataGridRow)sender;
// important: Remove the event again
dataGridRow.Loaded -= DataGrid_Row_Loaded;
NestedGridFieldProperty ngrProp = (NestedGridFieldProperty)dataGridRow.Item;
// Get the "presenter", which contains the cells
DataGridCellsPresenter presenter = coNeboTools.ConeboMisc.GetVisualChild<DataGridCellsPresenter>(dataGridRow);
// Get the cells in the presenter
var cells = GetVisualChildren<DataGridCell>(presenter);
// Get the underlying TextBox in column 0
TextBox underlyingTextBox = (TextBox)cells.ElementAt(0).Content;
// the Item property of the row contains the row data
var myData = dataGridRow.Item;
// do what ever is needed with the TextBlock
underlyingTextBox.Foreground = Brushes.Red;
}
// Static helper method to handle the visual tree
public static IEnumerable<T> GetVisualChildren<T>(DependencyObject dependencyObject)
where T : DependencyObject
{
if (dependencyObject != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(dependencyObject);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in GetVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
// Static helper method to handle the visual tree
public static childItem GetVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
foreach (childItem child in GetVisualChildren<childItem>(obj))
{
return child;
}
return null;
}

Adding button click event in datagridtemplatecolumn in user control from mainwindow programmatically

I have a MainWindow. It has a stackpanel myStack and some other things.
In stackPanel, there is a usercontrol (TaskGrid(_TG)) added programmatically.
In this UserControl, there is a DataGrid(dgEmployee), which have 4 template columns. the last column contains a button.
I am trying to assign the button click event from the mainwindow constructor and handle the event here.
Here are the codes:
in MainWindow.xaml
<Grid Grid.Row="2">
<StackPanel Name="myStack"/>
</Grid>
in MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
_TG = new TaskGrid();
_TD = new _1.TaskDetails();
_TM = new _1.TaskMaster();
myStack.Children.Add(_TG);
_AUC = ActiveUserControl.Grid;
foreach (object child in myStack.Children)
{
string childname = "";
if (child is FrameworkElement)
{
childname = (child as FrameworkElement).Name;
if (childname == "TaskGrid")
{
Grid dg = ((Grid)((UserControl)child).Content);
foreach (var item in dg.Children)
{
DataGridColumn b = ((DataGrid)item).Columns[3] as DataGridColumn;
}
}
}
}
}
And in TaskGrid.xaml, the only template column is given here
<DataGridTemplateColumn Width="30">
<DataGridTemplateColumn.CellTemplate>
<ItemContainerTemplate>
<Button Name="btnMaster" Background="Transparent">
<Button.Template>
<ControlTemplate>
<Image Source="ArrowRight.png"/>
</ControlTemplate>
</Button.Template>
</Button>
</ItemContainerTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have to assign the click event like
button.click += new RoutedEvent(button_click);
And later use button_click event in the MainWindow.xaml.cs
Wait until the UserControl has been loaded. You could then get a reference to the DataGrid using the following helper method that searches for an element of a specific type recursively in the visual tree.
private static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null)
return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
The same way you can get a reference to a specific cell:
public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
if (rowContainer != null)
{
System.Windows.Controls.Primitives.DataGridCellsPresenter presenter =
GetChildOfType<System.Windows.Controls.Primitives.DataGridCellsPresenter>(rowContainer);
if (presenter != null)
return presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return null;
}
Please refer to the following blog post for more information about this: https://blog.magnusmontin.net/2013/11/08/how-to-programmatically-select-and-focus-a-row-or-cell-in-a-datagrid-in-wpf/
Below is a full example for you. Note that the DataGrid may contain several rows and some of the rows may have been virtualized away. You will find more information about this on the link above.
public MainWindow()
{
InitializeComponent();
_TG = new TaskGrid();
_TD = new _1.TaskDetails();
_TM = new _1.TaskMaster();
myStack.Children.Add(_TG);
_AUC = ActiveUserControl.Grid;
_TG.Loaded += (s, e) =>
{
DataGrid dataGrid = GetChildOfType<DataGrid>(_TG);
if (dataGrid != null)
{
foreach (var item in dataGrid.Items)
{
DataGridRow dgr = dataGrid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (dgr != null)
{
DataGridCell cell = GetCell(dataGrid, dgr, 3); //<-- column index
if (cell != null)
{
Button button = GetChildOfType<Button>(cell);
if (button != null)
{
button.Click += new RoutedEvent(button_click);
}
}
}
}
}
};
}

ListView - Set focus on a control in a new row (UWP)

I have a ListView backed by an ObservableCollection. The user can add a new row, where in code I add a new object to the collection: array.Add(obj).
Now what I'd like to do is give focus to a TextBox in the new row. The problem is that I believe I need to wait until the UI is created, and I don't know of an event that will let me know when the new row is ready.
I've tried getting the new container and a reference to TextBox in ListView_SelectionChanged, but I was getting null return values on the new row.
I've tried using ListViewItem.Loaded, but this doesn't seem to be called for recycled rows.
I also tried ListViewItem.GotFocus, but this wasn't called after adding a new row in code.
If I knew when the controls on the ListViewItem were ready, I could then find the TextBox and set its focus.
Maybe I'm making this harder than it needs to be, but I'm not sure how to proceed.
I'm answering my own question. Below is what I came up with.
Xaml: (add two event handlers to Grid)
<DataTemplate x:Key="MyTemplate" x:DataType="model:Card">
<Grid GotFocus="ListViewGrid_GotFocus" DataContextChanged="ListViewGrid_DataContextChanged">
<StackPanel Orientation="Horizontal">
<TextBox Name="Text1" Text="{x:Bind Text1}" />
</StackPanel>
</Grid>
</DataTemplate>
Code:
MyListView.Items.VectorChanged += ListViewItems_VectorChanged; // in constructor
private void AddRow_Click(object sender, RoutedEventArgs e) {
card = ....
_newRowCard = card;
_array.Add(card);
}
private void ListViewItems_VectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs #event) {
// If new row added, at this point we can safely select and scroll to new item
if (_newRowCard != null) {
MyListView.SelectedIndex = MyListView.Items.Count - 1; // select row
MyListView.ScrollIntoView(MyListView.Items[MyListView.Items.Count - 1]); // scroll to bottom; this will make sure new row is visible and that DataContextChanged is called
}
}
private void ListViewGrid_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) {
// If new row added, at this point the UI is created and we can set focus to text box
if (_newRowCard != null) {
Grid grid = (Grid)sender;
Card card = (Card)grid.DataContext; // might be null
if (card == _newRowCard) {
TextBox textBox = FindControl<TextBox>(grid, typeof(TextBox), "Text1");
if (textBox != null) textBox.Focus(FocusState.Programmatic);
_newRowCard = null;
}
}
}
private void ListViewGrid_GotFocus(object sender, RoutedEventArgs e) {
// If user clicks on a control in the row, select entire row
MyListView.SelectedItem = (sender as Grid).DataContext;
}
public static T FindControl<T>(UIElement parent, Type targetType, string ControlName) where T : FrameworkElement {
if (parent == null) return null;
if (parent.GetType() == targetType && ((T)parent).Name == ControlName) return (T)parent;
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++) {
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
T result = FindControl<T>(child, targetType, ControlName);
if (result != null) return result;
}
return null;
}

Get selected row item in DataGrid WPF doesn't work

Im tring to get Selected row item.
I have been readed that it should be worked :
<DataGrid ItemsSource="{Binding Path=Customers}"
SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}"/>
Customer customer = (Customer)myDataGrid.SelectedItem;
In the first xaml - I put it, it doesnt error or something I just dont know how to use it.. How in the c# code I can get the selected row ?
In the C# line code It error. The visual studio doesn't exist Customer.
I would thankfull to help. :) Thanks.
rly hard to know what exactly you wanted :) but here is an example I just put togetter on how to drag info out of the datagrid.
Did not implament alot of those bindings in it so this is pure getting the info.
Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" Name="dg"/>
<Button Grid.Row="1" Name="btn" Click="btn_Click" />
</Grid>
Codebehind
List<SomeInfo> list = new List<SomeInfo>();
public MainWindow()
{
InitializeComponent();
list.Add(new SomeInfo() { Name = "PC", Description = "Computer", ID = 1 });
list.Add(new SomeInfo() { Name = "PS", Description = "Playstation", ID = 2 });
list.Add(new SomeInfo() { Name = "XB", Description = "Xbox", ID = 3 });
this.dg.ItemsSource = list;
}
public class SomeInfo
{
public string Name { get; set; }
public string Description { get; set; }
public int ID { get; set; }
}
private void btn_Click(object sender, RoutedEventArgs e)
{
if (dg.SelectedIndex != -1)
{
DataGrid dataGrid = sender as DataGrid;
DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(dg.SelectedIndex);
DataGridCell RowColumn = dg.Columns[0].GetCellContent(row).Parent as DataGridCell;
btn.Content = ((TextBlock)RowColumn.Content).Text;
}
}
the btn_click does all the gathering info stuff where the last 2 makes my datagrid for testing.
Hope it will help you out :)
------------------Edit--------------------------
from the comment this below is what you would need only
private void btn_Click(object sender, RoutedEventArgs e)
{
if (dg.SelectedIndex != -1)
{
DataGrid dataGrid = sender as DataGrid;
DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(dg.SelectedIndex);
DataGridCell RowColumn = dg.Columns[0].GetCellContent(row).Parent as DataGridCell;
btn.Content = ((TextBlock)RowColumn.Content).Text;
}
}
dg = your datagrid
dg.Columns[0] = change 0 into what column you want
info from btn.Content = what you want the content to be
--------------EDIT 2------------
To get the selected row's index all you need is
int index = dg.SelectedIndex;
btn.Content = index;
or if you dont want to store the integar
btn.Content = dg.SelectedIndex;
The grid defined in a very incomplete.
Council to explicitly all columns that compose it with the individual binding
In this case I used the event doubleClick but you have to choose the right event to you
By code for example try this:
In XAML add:
<DataGrid x:Name="MyDataGrid" x:FieldModifier="public" MouseDoubleClick="MyDataGrid_MouseDoubleClick" ItemsSource="{Binding Path=Customers}"
SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}"/>
And in behind c# code this:
private void MyDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender != null)
{
DataGrid grid = sender as DataGrid;
if (grid != null && grid.SelectedItems != null && grid.SelectedItems.Count == 1)
{
var selectedRow = grid.SelectedItem as MyObject;
try
{
Mouse.OverrideCursor = Cursors.Wait;
//TODO YOUR OPERATION ON selectdRow
}
finally
{
Mouse.OverrideCursor = null;
}
}
}
}

DataGrid Select Column

I am trying to programatically select an entire column in a WPF DataGrid. My code seems to work but it is REALLY slow! I'm guessing it is because it is continually having to call ScrollIntoView. Can someone help me with a solution to speed it up or an alternative to select the entire column?
public static void SelectColumn(DataGrid grid, int column)
{
for (int i = 0; i < grid.Items.Count; i++)
{
// Select each cell in this column
var cell = DataGridHelper.GetCell(grid, i, column);
if (cell != null)
{
cell.IsSelected = true;
}
}
DataGridHelper.GetCell(grid, 0, column).Focus();
}
public static DataGridCell GetCell(DataGrid grid, int row, int column)
{
DataGridRow rowContainer = GetRow(grid, row);
if (rowContainer != null)
{
DataGridCellsPresenter presenter = TreeHelper.GetVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter == null)
{
// may be virtualized, bring into view and try again
grid.ScrollIntoView(rowContainer, grid.Columns[column]);
presenter = TreeHelper.GetVisualChild<DataGridCellsPresenter>(rowContainer);
}
if (presenter != null)
{
// try to get the cell but it may possibly be virtualized
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
if (cell == null)
{
// may be virtualized, bring into view and try again
grid.ScrollIntoView(rowContainer, grid.Columns[column]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
}
return cell;
}
}
return null;
}
public static DataGridRow GetRow(DataGrid grid, int index)
{
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// may be virtualized, bring into view and try again
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
UPDATE:
I'm trying out the solution suggested by #ianschol. Here is what I have (I bind in code behind b/c I don't know how many columns I need until runtime):
for (int i = 0; i < this.CurrentData.Data[0].Length; i++)
{
TheGrid.Columns.Add(
new DataGridTextColumn
{
Header = (this.CurrentData.Rank > 1) ? string.Format(this.culture, headerFormatString, i + 1) : string.Empty,
Binding = new Binding(string.Format("[{0}].DataValue", i)) { ValidatesOnDataErrors = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged },
Width = DataGridLength.Auto,
ElementStyle = new Style
{
TargetType = typeof(TextBlock),
Triggers = { this.errorTrigger }
},
EditingElementStyle = new Style
{
TargetType = typeof(TextBox),
Triggers = { this.errorTrigger }
},
CellStyle = new Style
{
TargetType = typeof(DataGridCell),
Setters =
{
new Setter
{
Property = DataGridCell.IsSelectedProperty,
Value = new Binding(string.Format("[{0}].IsSelected", i)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged },
}
},
}
});
}
and my IsSelected property:
private bool isSelected = false;
public bool IsSelected
{
get
{
return this.isSelected;
}
set
{
this.isSelected = value;
OnPropertyChanged("IsSelected");
}
}
And the new SelectColumn code:
public static void SelectColumn(DataGrid grid, int column)
{
for (int i = 0; i < grid.Items.Count; i++)
{
// Select each cell in this column
((DataItem[])(grid.Items[i]))[column].IsSelected = true;
}
}
The problem is that if I update the IsSelected property in code, it updates the GUI (kinda, its quirky) but not vice versa. I.e. if I select a cell/row in the GUI, it doesn't call the property setter in the code. As you can see the binding is TwoWay so I'm not sure the issue.
Another UPDATE: The issue definitely seems to be with virtualization. If i turn off virtualization (VirtualizingStackPanel.IsVirtualizing="False" ) it works fine.
A more effective approach would probably be to have IsSelected properties on the DataSource's class, such that each column has a corresponding "IsSelected" property.
public class MyData : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
Notify("Name");
}
}
private bool nameSelected = false;
public bool NameSelected
{
get { return nameSelected; }
set
{
nameSelected = value;
Notify("NameSelected");
}
}
//... etc ...
}
Next, you can alter the CellStyle for each Column to bind the cells' IsSelected property to the related IsSelected property on the class.
<DataGrid ItemsSource="{Binding Users}" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="scratchGrid" CanUserAddRows="False"
VerticalScrollBarVisibility="Auto" SelectionUnit="Cell">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="User Name" Width="200">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="IsSelected" Value="{Binding NameSelected}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Age}" Header="User Age" Width="80">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="IsSelected" Value="{Binding AgeSelected}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Finally, implement your select-all code like so (this does select-all on Age, you may want to make a more generic/elegant implementation ;) ) :
foreach (MyData user in Users)
{
user.AgeSelected = true;
}
You'll have to take care to make sure all your NotifyPropertyChanged behavior is lined up, since you're expecting the grid to recognize that properties inside its bound collection are being updated.

Categories