Get DataGrid Row from ComboBox Event - c#

I have a DataGrid with 2 columns defined in XAML as
<DataGrid x:Name="marketInfodg" Grid.Row="1"
ItemsSource = "{Binding ElementName=This, Path=dataTableTest}"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Department Id" x:Name="comboboxColumn1"
SelectedValueBinding="{Binding Department Id}" />
<DataGridTemplateColumn x:Name="DataGridTempCol" Header="Selection">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
x: Name = "combo"
SelectedValue = "{Binding Selection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource = "{Binding comboBoxSelections, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
DropDownOpened = "combo_DropDownOpened"
DisplayMemberPath = "Key"
IsEditable="True">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The constructor code is
_dataTableTest = new DataTable();
DataColumn dc = new DataColumn();
dc.ReadOnly = false;
dc.DataType = typeof(String);
dc.ColumnName = "Department Id";
_dataTableTest.Columns.Add(dc);
DataColumn dc1 = new DataColumn();
dc1.ReadOnly = false;
dc1.DataType = typeof(KeyValuePair<string, double>);
dc1.ColumnName = "Selection";
_dataTableTest.Columns.Add(dc1);
marketInfodg.ItemsSource = _dataTableTest.DefaultView;
var row = _dataTableTest.NewRow();
row = _dataTableTest.NewRow();
_dataTableTest.Rows.Add(row);
row["Department Id"] = "X567";
row["Selection"] = (KeyValuePair<string, double>)comboBoxSelections[0];
which effectively sets a single row as column "Department Id" = "X567" and a second column is a combobox set to the first item in comboBoxSelections[0]
The combo_DropDownOpened event fires when any Combobox drop down is opened (obviously) and I can set the variable cb based on the sender, using
private void combo_DropDownOpened(object sender, EventArgs e)
{
var cb = ((System.Windows.Controls.ComboBox)sender);
}
How do I also get the associated Row (all columns in the Row) and RowIndex/number of the firing ComboBox in the combo_DropDownOpened event?

ComboBox lies in the visual tree of the DataGrid. If you travel up Visual Tree you will find the DataGridRow on the way to the top to the DataGrid.
You can use the VisualTreeHelper class to walk up to the Visual tree. Generally, you can use this method to find any parent in the Visual tree of your control. Put this method in some Utility class and use whenever you feel like walking up to Visual tree for your control to find any parent -
public static Parent FindParent<Parent>(DependencyObject child)
where Parent : DependencyObject
{
DependencyObject parentObject = child;
//We are not dealing with Visual, so either we need to fnd parent or
//get Visual to get parent from Parent Heirarchy.
while (!((parentObject is System.Windows.Media.Visual)
|| (parentObject is System.Windows.Media.Media3D.Visual3D)))
{
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
parentObject = (parentObject as FrameworkContentElement).Parent;
}
}
//We have not found parent yet , and we have now visual to work with.
parentObject = VisualTreeHelper.GetParent(parentObject);
//check if the parent matches the type we're looking for
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
//use recursion to proceed with next level
return FindParent<Parent>(parentObject);
}
}
Now, in your dropDown event handler you can use the above function to find the DataGridRow like this -
private void combo_DropDownOpened(object sender, EventArgs e)
{
var cb = ((System.Windows.Controls.ComboBox)sender);
DataGridRow dataGridRow = FindParent<DataGridRow>(cb);
int index = dataGridRow.GetIndex();
}

Related

Accessing control from datatemplate inside of gridview

I have a ListView that displays a table with various columns. Each cell of a row in the table contains a different type of control; I am trying to allow the user to edit the data in each row by selecting a row and double-clicking it to make the cells editable. So I have been able to get all of them to work with the exception of the column that contains ComboBoxes.
XAML code:
This is the XAML code for the ListView. It has about 7 columns but I am focusing on the column with ComboBoxes as depicted here.
<ListView x:Name="MyListView" IsSynchronizedWithCurrentItem="True" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,12,0,0" Height="315" Width="560" ItemsSource="{Binding People}">
<ListView.View>
<GridView>
<!-- More Grid column code here -->
<GridViewColumn Header="Fleet" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="locationCmboBx" ItemsSource="{Binding DataContext.SchoolLocations, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}" Loaded="OnCmboBxLoad" IsEnabled="False" Width="55" HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- More Grid column code here -->
</GridView>
</ListView.View>
</ListView>
C# code:
So here in the code-behind I am trying to use the VisualTreeHelper as recommended by others to get access to the locationsCmboBx (ComboBox) nested inside of the DataTemplate, CellTemplate and other XAML headers in the ListView.
// More code before here
ListView listViewItem = (ListView)(MyListView.ItemContainerGenerator.ContainerFromItem(MyListView));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(listViewItem);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
ComboBox comboBox = (ComboBox)myDataTemplate.FindName("locationsCmboBx", myContentPresenter);
// More code before here
private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
So everything I have works but when I debug through the code and get to the FindName function ComboBox is null. Ultimately, I want to set the IsEnabled property on it and get the SelectedValue from the locationsCmboBx. I believe I am missing something but not sure of what. Any help would be appreciated?
The problem in your code is a typo by combo box's name in XAML locationCmboBx and in code behind is locationsCmboBx.
The code:
ListView listViewItem = (ListView)(MyListView.ItemContainerGenerator.ContainerFromItem(MyListView));
is also wrong. Argument of ContainerFromItem() must be a data item. Returned type is also wrong. It must be ListViewItem
I would recommend you to use a ViewModel + bindings and not a code behind to access the data. So you can avoid such a tipo errors. See also: Detect in XAML broken bindings already at compile time
I found this information:
public static class ListViewHelper
{
public static FrameworkElement GetElementFromCellTemplate(ListView listView, Int32 column, Int32 row, String name)
{
if (row >= listView.Items.Count || row < 0)
{
throw new ArgumentOutOfRangeException('row');
}
GridView gridView = listView.View as GridView;
if (gridView == null) { return null; }
if (column >= gridView.Columns.Count || column < 0)
{
throw new ArgumentOutOfRangeException('column');
}
ListViewItem item = listView.ItemContainerGenerator.ContainerFromItem(listView.Items[row]) as ListViewItem;
if (item != null)
{
GridViewRowPresenter rowPresenter = GetFrameworkElementByName(item);
if (rowPresenter != null)
{
ContentPresenter templatedParent = VisualTreeHelper.GetChild(rowPresenter, column) as ContentPresenter;
DataTemplate dataTemplate = gridView.Columns[column].CellTemplate;
if (dataTemplate != null && templatedParent != null)
{
return dataTemplate.FindName(name, templatedParent) as FrameworkElement;
}
}
}
return null;
}
private static T GetFrameworkElementByName(FrameworkElement referenceElement) where T : FrameworkElement
{
FrameworkElement child = null;
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
{
child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
System.Diagnostics.Debug.WriteLine(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
else if (child != null)
{
child = GetFrameworkElementByName(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
}
}
return child as T;
}
}
Source: How do I access the ui element at a row/cell in my GridView?

Check checkboxes in combobox

I have a combobox which has checkbox as its combobox.itemtemplate.
<ComboBox Name="comboBoxTest"
SelectedValuePath="Test"
SelectedItem="{Binding SelectedTest, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding SelectedTest, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBoxBase.TextChanged ="comboBoxTest_TextChanged" Grid.ColumnSpan="2"
TextSearch.TextPath="Model" >
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="checkBoxTest"
Content="{Binding Test}"
Click="checkBoxTest_Click"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
An “--Select All--” item has been add into the result list when the result list is produced.
When user checks on the “All” item, the other checkboxes should be checked as well.
I am using the codes below but it doesn’t work.
if (checkBoxTest.Content.ToString().Equals("--Select All--"))
{
foreach (object item in comboBoxTest.Items)
{
ComboBoxItem comboBoxItem = comboBoxTest.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
FrameworkElement element = comboBoxItem.ContentTemplate.LoadContent() as FrameworkElement;
CheckBox checkBox = element.FindName("checkBoxTest") as CheckBox;
checkBox.IsChecked = true;
}
}
There are few issues in your code let me first tell you about those issue.
your if condition for identifying the "Select All" checkbox is incorrect. Your need to use Contains() instead of equals()
The checkbox you are fetching is not the correct one within the comboBox Item. If you try to see the checkBox.Content property you will see null as result.
See below code to select all the checkboxes within the comboBox when "Select All" checkbox is checked.
Your Checkbox Click event should be as below.
private void checkBoxTest_Click(object sender, RoutedEventArgs e)
{
CheckBox checkBoxTest = e.Source as CheckBox;
if (checkBoxTest == null)
return;
if (checkBoxTest.Content.ToString().Contains("Select All") && checkBoxTest.IsChecked == true)
{
foreach (object item in comboBoxTest.Items)
{
ComboBoxItem comboBoxItem = comboBoxTest.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
if (comboBoxItem == null)
{
return;
}
CheckBox checkBox = FindVisualChildByName<CheckBox>(comboBoxItem, "checkBoxTest");
checkBox.IsChecked = true;
}
}
}
I have added a new method to fetch the visual child within any element from the name of the child and its type.
private static T FindVisualChildByName<T>(DependencyObject parent, string name) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
string controlName = child.GetValue(NameProperty) as string;
if (controlName == name)
{
return child as T;
}
T result = FindVisualChildByName<T>(child, name);
if (result != null)
return result;
}
return null;
}
use CompositeCollection inside your ComboBox, check below answers for more information
Combobox and checkbox with "Select All" Checkbox with binding in wpf
How can I insert a "Select All" item at the top of a ComboBox with its ItemsSource set?
Binding/Triggering "Select all"-CheckBox ComboBoxItem in WPF

Obtain DataGrid inside ItemsControl from the binded item

I have an ItemsControl that uses DataGrid in its template like this:
<ItemsControl Name="icDists" ItemsSource="{Binding Dists}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding}" Width="150" Margin="5" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Key" Binding="{Binding Key}" Width="1*" />
<DataGridTextColumn Header="Value" Binding="{Binding Value}" Width="1*" />
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ItemsControl is binded to a Dists property in my model that looks like this:
ObservableCollection<Dictionary<string, string>> Dists;
How can I obtain the DataGrid that corresponds to an item in the Dists property? I've tried with this code, which gives me a ContentPresenter but I don't know how to obtain the DataGrid from it:
var d = Dists[i];
var uiElement = (UIElement)icDistribucion.ItemContainerGenerator.ContainerFromItem(d);
I've tried walking up the tree with VisualHelper.GetParent but couldn't find the DataGrid.
Need to search the VisualTree if you want to do something like that. Though I recommend reading a bit more on MVVM patterns. But here is what you want.
using System.Windows.Media;
private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(parentElement);
if (count == 0)
return null;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
return null;
}
Now after you set your ItemsSource and the ItemControl is ready. I'm just going to do this in the Loaded event.
private void icDists_Loaded(object sender, RoutedEventArgs e)
{
// get the container for the first index
var item = this.icDists.ItemContainerGenerator.ContainerFromIndex(0);
// var item = this.icDists.ItemContainerGenerator.ContainerFromItem(item_object); // you can also get it from an item if you pass the item in the ItemsSource correctly
// find the DataGrid for the first container
DataGrid dg = FindFirstElementInVisualTree<DataGrid>(item);
// at this point dg should be the DataGrid of the first item in your list
}

DataGrid.SelectedItem not bound to currently selected cell

Ran into this issue on several occasions.
When finding SelectedItem or selected column on say right click menu or selecting combo box in a cell. The SelectedItem will be null or the previously selected row.
private void ComboBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
// Correct
m_BeginEditString = ((ComboBox)sender).SelectedValue.ToString();
// Wrong. selected item is last selected row, example clicking directly on combobox will not select row, and be null.
m_BeginEditRow = (RowItem)MyDataGrid.SelectedItem;
}
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding myItem, Mode=TwoWay,
NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Source={StaticResource enum}}"
SelectionChanged="ComboBox_Changed"
LostKeyboardFocus="ComboBox_LostKeyboardFocus"
GotKeyboardFocus="ComboBox_GotKeyboardFocus" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Solved by doing it a completely different way, thanks #Ramesh Muthiah for direction:
private void ComboBox_Changed(object sender, SelectionChangedEventArgs e) {
if (((ComboBox)sender).IsLoaded) { // disregard SelectionChangedEvent fired on population from binding
if (e.RemovedItems.Count != 0) {
for (Visual visual = (Visual)sender; visual != null; visual = (Visual)VisualTreeHelper.GetParent(visual)) { // Traverse tree to find corred selected item
if (visual is DataGridRow) {
DataGridRow row = (DataGridRow)visual;
m_BeginEditRow = new MyRowItem((MyRowItem)row.Item); // Copy constructor, otherwise passed by reference
break;
}
}
MyEnum newItem = (MyEnum)e.AddedItems[0];
MyEnum oldItem = (MyEnum)e.RemovedItems[0];
if (m_BeginEditRow.Combo1 == newItem) {
m_BeginEditRow.Combo1 = oldItem;
} else {
m_BeginEditRow.Combo2 = oldItem;
}
DoStuff(m_BeginEditRow, false);
}
}
}
Instead of accessing the selected Item directly, you can access through the parent object and try to access whatever you want. This is alternative approach. I hopes this helps you
Combobox objMyButton = null;
if (sender is Combobox)
{
objMyButton = (sender as Combobox );
}
//You can access the parent object which means corresponding DataGridRow and do whatever you want
for (var vis = sender as Visual; vis != null; vis = VisualTreeHelper.GetParent(vis) as Visual)
if (vis is DataGridRow)
{
var row = (DataGridRow)vis;
break;
}

How to put data in WPf Data Grid specific cell

i m using DataGrid in WPF
I want to put some specific string in some specific cell . How can we do this.
I am putting value this way .
//pPredicate is a string variable havign some value
// i m assigning in a particualr cell (3rd col)
if (GetCell(3).Content is TextBlock)
{
((TextBlock)(GetCell(3).Content)).Text = pPredicate;
}
else // TextBox
{
((TextBox)(GetCell(3).Content)).Text = pPredicate;
}
private DataGridCell GetCell(int column)
{
DataGridRow rowContainer = GetRow();
if (rowContainer != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
// Try to get the cell but it may possibly be virtualized.
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
if (cell == null)
{
// Now try to bring into view and retreive the cell.
customDataGrid.UCdataGridView.ScrollIntoView(rowContainer, customDataGrid.UCdataGridView.Columns[column]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
}
return cell;
}
return null;
}
private DataGridRow GetRow()
{
DataGridRow row = (DataGridRow)customDataGrid.UCdataGridView.ItemContainerGenerator.ContainerFromIndex(_currentRowIndex);
if (row == null)
{
// May be virtualized, bring into view and try again.
customDataGrid.UCdataGridView.UpdateLayout();
customDataGrid.UCdataGridView.ScrollIntoView(customDataGrid.UCdataGridView.Items[_currentRowIndex]);
row = (DataGridRow)customDataGrid.UCdataGridView.ItemContainerGenerator.ContainerFromIndex(_currentRowIndex);
}
return row;
}
private T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
the problem in top most statmetnts is the row is in edit mode the grid contains textbox while otherwise grid contains textblock. When i m putting value in textblock it does not persisting while putting value in textbox persists.
XAML part of custom DataGrid
<WPFtoolkit:DataGridTextColumn x:Name="dgName" Binding="{Binding Path=Name}" Header="Name" MinWidth="100" Visibility="Collapsed"/>
<WPFtoolkit:DataGridTextColumn x:Name="dgPredicates" Binding="{Binding Path=Predicate}" Header="Predicate" MinWidth="100"
Visibility="Collapsed"/>
<WPFtoolkit:DataGridTemplateColumn Header="Delete" IsReadOnly="True"
Visibility="Collapsed" MaxWidth="80" Width ="*">
<WPFtoolkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="../images/Delete.png" Height="15" Width="15"/>
</DataTemplate>
</WPFtoolkit:DataGridTemplateColumn.CellTemplate>
</WPFtoolkit:DataGridTemplateColumn>
<WPFtoolkit:DataGridTextColumn Binding="{Binding Path=TreeType}" Header="Tree Type" Width="50"
Visibility="Collapsed"/>
</WPFtoolkit:DataGrid.Columns>
</WPFtoolkit:DataGrid>
If I understand your question correctly then you want the changes that you make to the TextBlock/TextBox to be propagated to the underlying DataTable. This works for a TextBox but not for a TextBlock.
The reason for this is that TextBox.Text binds TwoWay by default but TextBlock.Text binds OneWay. If you want this to work you have to change all your TextBlock bindings to explicitly use TwoWay like
<TextBlock Text="{Binding Path=SomeProperty, Mode=TwoWay}"/>
if you dont want interest xaml , this code maybe work for yor need.
(xx ,yy is row and colonm number)
DataGridCell cell =(YourDgName.Columns[XX].GetCellContent(DgCagrilar.Items[YY])).Parent s DataGridCell;
if (cell.Background == Brushes.Pink)
cell.Background = Brushes.Plum;
else
cell.Background = Brushes.Pink;

Categories

Resources