How to reference Checkbox which is defined in DataTemplate - c#

I have a datagrid as below. I want to reference CheckBox which is named ckbSelectedAll in code behind. WPF does not allow to reference with the name in code behind. Which way should i follow ?
<DataGrid x:Name="deckGrid" ItemsSource="{Binding DeckList}" AutoGenerateColumns="False"
SelectionMode="Single" Margin="10,10,0,0" SelectionUnit="FullRow" CanUserAddRows="False" IsReadOnly="True">
<DataGrid.Columns >
<DataGridTemplateColumn Width="70">
<DataGridTemplateColumn.HeaderTemplate >
<DataTemplate>
<CheckBox x:Name="ckbSelectedAll" IsThreeState="True" Margin="10,0,0,0" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked">
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

I answered a similar question regarding accessing buttons from with a DataTemplate of a ListView. Fortunately the process is very much the same. Here, I enumerate each of the DataGrid items from the ItemSource. I randomly uncheck a couple of items that I have stored in a List. I process the items from a Button_Click but doing it from the Window constructor after a call to InitializeComponent() should yield the same results.
List<CheckBox> checkboxes =
new List<CheckBox>();
private void Button_Click(object sender, RoutedEventArgs e)
{
deckGrid.Items
.Cast<dynamic>()
.ToList()
.ForEach(item => {
var checkitem =
(deckGrid
.ItemContainerGenerator
.ContainerFromItem(item));
CheckBox checkbox =
FindVisualChild<CheckBox>
(checkitem);
checkboxes
.Add(checkbox);
});
checkboxes[1]
.IsChecked = false;
checkboxes[3]
.IsChecked = false;
}
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;
}

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?

Get Nested ListView by name in Code Behind

I have the following nested ListViews:
<!-- Display a list of each CustomTab -->
<!-- Drag & Drop functionality implemented in code behind using ListViewDragDropManager -->
<ListView Name="TasksListView"
ItemsSource="{Binding Model.TaskCollection, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
.....
<!-- Parameters List -->
<ListView Name="TaskParameterListView"
ItemsSource="{Binding TaskParameterCollection, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
.....
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I need to get access to the TaskParameterListView in code behind. How can I get a reference to this object?
With the TasksListView I can access it simply by calling this.TasksListView
Here is how you can find inner ListView using FrameworkTemplate.FindName:
DependencyObject container = TasksListView
.ItemContainerGenerator
.ContainerFromItem(TasksListView.SelectedItem);
if (container != null)
{
ContentPresenter presenter = GetPresenter(container);
ListView listView = presenter
.ContentTemplate
.FindName("TaskParameterListView", presenter) as ListView;
}
Additional method to find a ContentPresenter inside the ListBoxItem:
private static ContentPresenter GetPresenter(DependencyObject reference)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(reference); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(reference, i);
ContentPresenter presenter =
child as ContentPresenter ??
GetPresenter(child);
if (presenter != null)
{ return presenter; }
}
return null;
}

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
}

Get DataGrid Row from ComboBox Event

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();
}

Categories

Resources