WPF How to access control from DataTemplate - c#

I have a datatemplate which contains a grid and inside the grid I have a combobox.
<DataTemplate x:Key="ShowAsExpanded">
<Grid>
<ComboBox Name ="myCombo" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="5"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource MyItems}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</Grid>
</DataTemplate>
I then I have a grid that refers to that template through styling.
<Grid>
<ContentPresenter Name="_contentPresenter" Style="{DynamicResource StyleWithCollapse}" Content="{Binding}" />
</Grid>
How can I access through code behing the myCombo to basically set its DataContext?

Three ways which I know of.
1.Use FindName
ComboBox myCombo =
_contentPresenter.ContentTemplate.FindName("myCombo",
_contentPresenter) as ComboBox;
2.Add the Loaded event to the ComboBox and access it from there
<ComboBox Name ="myCombo" Loaded="myCombo_Loaded" ...
private void myCombo_Loaded(object sender, RoutedEventArgs e)
{
ComboBox myCombo = sender as ComboBox;
// Do things..
}
3.Find it in the Visual Tree
private void SomeMethod()
{
ComboBox myCombo = GetVisualChild<ComboBox>(_contentPresenter);
}
private T GetVisualChild<T>(DependencyObject 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;
}

First of all, I can't even find the relation between the Resource (ShowAsExpanded) and the usage inside the ContentPresenter. But for the moment, let's assume that the DynamicResource should point to ShowAsExpanded.
You can't and shouldn't access the combobox via code. You should bind the datacontext to the grid that uses the style. If you don't want to do that, you will have to find the content at runtime and search for the child combobox.

you need to use FindName. check out http://msdn.microsoft.com/en-us/library/system.windows.frameworktemplate.findname.aspx

Related

Windows.UI.Xaml.Controls.TreeView w/ Selection="Multiple", where is the checkbox selected/unselected event?

I am using Windows.UI.Xaml.Controls.TreeView along w/ Windows.UI.Xaml.Controls.TreeViewItem with Selection="Multiple".
<TreeView Name="DessertTree" SelectionMode="Multiple" ItemsSource="{x:Bind DataSource}" ItemInvoked="{x:Bind OnSelectionChanged, Mode=OneTime}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<TreeViewItem ItemsSource="{x:Bind Children}" Content="{x:Bind Name}"/>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I cannot seem to find a way to have an event fired for a state change of the checkbox associated with the TreeViewItem. Does anyone have any information on this? I see that the TreeView in WPF has a trigger for change of the checkbox state, so I would think the same functionality is available.
The only trigger I found that is somewhat similar is ItemInvoked, but that does not register selection events on the checkbox, only if the label is clicked.
You should be able to handle the Tapped event for the TreeViewItem, find the CheckBox in the visual tree and examine its IsChecked property:
private void OnTreeViewItemTapped(object sender, TappedRoutedEventArgs e)
{
TreeViewItem tvi = (TreeViewItem)sender;
CheckBox chk = FindVisualChild<CheckBox>(tvi);
bool? isChecked = chk.IsChecked;
}
private static T FindVisualChild<T>(DependencyObject visual) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(visual, i);
if (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null)
return correctlyTyped;
T descendent = FindVisualChild<T>(child);
if (descendent != null)
return descendent;
}
}
return null;
}

how to access label name in nested listview datatemplate on a check box event

I have defined a label with name and I'm trying to access it but no luck. Let me explain my problem with my code.
<ListView Name="gridListView" ItemsSource="{Binding... }">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Vertical">
<Label x:Name="vLabel" Content="{Binding VCValue}"/>
<ListView Name="checkBoxListView" ItemsSource="{Binding CList}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Margin="5" Click="CheckBox_Click" IsChecked="{Binding SelectedValue, Mode=TwoWay}" Content="{Binding Current, Mode=OneWay }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the above code, I have two listview, gridListView and checkBoxListView. Here, I want to access the label vLabel which is inside the datatemplate of gridListView when the one of the value in checkbox(which is inside checkBoxListView) is clicked.
I understand it can't be accessed directly as its within datatemplate so i tried below code as suggested in other forums but gridListView.SelectedIndex is always -1 so i know i'm not doing the right thing. When I just hardcoded gridListView.SelectedIndex to index 0, 1 or 2 its giving me the right value of vLabel so the below code will work if gridListView.SelectedIndex is correct.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox chk =(CheckBox)sender;
int index = gridListView.Items.IndexOf(chk.DataContext);
ListViewItem item = gridListView.ItemContainerGenerator.ContainerFromIndex(gridListView.SelectedIndex) as ListViewItem;
if (item!=null)
{
//get the item's template parent
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = gridListView.ItemTemplate;
if (dataTemplate != null && templateParent != null)
{
var lab = dataTemplate.FindName("vLabel", templateParent) as Label;
var v = lab.Content;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
//I can post this function if need be
....
}
Appreciate any help that will help me access vLabel.
Thanks in advance
you are setting focusable to false, so you cant select the item by clicking. also the checkbox-checked happens before the selection, even if you were to set focusable to true, so selected index would still be -1.
you can find your label simply like this:
public Label FindLabel(CheckBox checkBox)
{
var listView = VisualTreeHelper.GetParent(checkBox);
while (listView.GetType() != typeof(ListView))
{
listView = VisualTreeHelper.GetParent(listView);
}
return (listView as FrameworkElement).FindName("vLabel") as Label;
}
but i suggest you tell us what you want to achieve, because this doesnt seem like a clean solution.
can you perhaps do this on your StackPanel:
<StackPanel CheckBox.Checked="CheckBox_Click" Orientation="Vertical">
and then access your two desired properties as following:
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var outerItem = (sender as FrameworkElement).DataContext;
var innerItem = (e.OriginalSource as FrameworkElement).DataContext;
}
and then do whatever you want to do?
Thank you for all your guidance. I did get hint from Milan's code to achieve solution for my issue. Here, I'm trying to get reference parent of listviewItem(i.e stackpanel) and then access its child. In my case, stack panels child at index 0 is Label and at index 1 is ListView. So then I go through visualtreehelper to get reference to its child at index 0 which is what i need access to. So here is the code snippet.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
//to access parent of the checkbox
ListViewItem listViewItem =
GetVisualAncestor<ListViewItem>((DependencyObject)sender);
//to access parent of the listViewItem(which is parent of checkbox)
StackPanel stackPanel =
GetVisualAncestor<StackPanel>((DependencyObject)listViewItem);
int childCount = VisualTreeHelper.GetChildrenCount(stackPanel);
//to access child of stackpanel to access the current vLabel
var vValue = VisualTreeHelper.GetChild(stackPanel, 0) as Label;
}
private static T GetVisualAncestor<T>(DependencyObject o)where T : DependecyObject
{
...
}

Hiding a sibling listbox when a textblock is clicked

I was wonder if any one knows how to change the visibility of a listbox within a DataTemplate when a sibling is clicked. The DataTemplate is being used on a listbox. The following is an example of the xaml I'm using:
<DataTemplate x:Key="Template1">
<StackPanel Margin="110,0,0,0">
<TextBlock Text="{Binding Name}" />
<TextBlock Name="ShowHide" Text="Hide" Tap="ShowHide_Tap" />
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" ItemTemplate="{StaticResource Template2}"/>
</StackPanel>
</DataTemplate>
The following is my attempt but I can't use the FindName
private void ShowHide_Click(object sender, System.Windows.Input.GestureEventArgs e)
{
var item = sender as TextBlock;
ListBox Listbox = null;
if (item != null)
{
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = templateParent.ContentTemplate;
if (dataTemplate != null && templateParent != null)
{
Listbox = templateParent.FindName("Listbox1") as ListBox;
}
if (Listbox != null)
{
MessageBox.Show(String.Format("ERROR!"));
}
else
Listbox.Visibility = Visibility.Collapsed;
}
}
private static T GetFrameworkElementByName<T>(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<T>(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
}
}
return child as T;
}
If any one has any insights they would be much appreciated,
Thanks.
It so happens that the Blend SDK provides functionality for this -- you can even use XAML only, no code behind. Just use an EventTrigger (on the "Tap" event) along with a ChangePropertyAction. Here's how it looks:
<TextBlock Name="ShowHide" Text="Hide" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<ei:ChangePropertyAction TargetName="Listbox1"
PropertyName="Visibility" Value="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" />
Note that this requires you to add references to the following Extensions:
System.Windows.Interactivity
Microsoft.Expression.Interactions
Reference them in XAML with the namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Welcome to StackOverflow!
Generally speaking this is not the way to use WPF, especially if you're using DataTemplates. The purpose of the view in WPF is to display the view model and fire off user events, nothing more. By changing the Data Template at run-time you're effectively storing the state of the view inside the view itself. This runs totally against the grain of how WPF was designed to be used.
To do this properly your view model (i.e. the class with the SecondList property) should have an extra property called something like ListBoxVisibility and you would bind your listbox's Visibility member to that. An even cleaner method is to use a bool and then use a converter in the view to convert it from type bool to type Visibility. Either way the view model should also have a property of type ICommand (e.g. OnButtonPressedCommand) that the button invokes when the user presses it. The handler for OnButtonPressedCommand, which should also be in the view model, then sets ListBoxVisible or whatever to the value you want which then propagates through to the list box. Doing things this way keeps good separation of concerns and means the view model can be created and its visibility-changing behavior unit tested independently without having to create the view itself.

Preselect Value of Treeview inside combobox

I am looking to implement a treeview inside a combobox. Basically I want it to show as a combobox when collapsed but a treeview inside combo box when expanded. When a user clicks on a node, I want it to show in the collapsed combo box. I have got this working so far.
The problem I am having is that how do I show a default value from c# when this combo box is loaded. Please help guys as I am running out of ideas :)
Thanks in advance.
Data Template
<DataTemplate x:Key="TreeViewExpanded" >
<StackPanel>
<TreeView x:Name="DPointTree" Margin="5" ItemsSource="{Binding Datapoint}"
Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBox}}}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Field}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding = "{Binding Path=SelectedFieldName, Mode=TwoWay}" Value = "">
<Setter Property = "Visibility" Value = "Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Here is the Xaml
<ComboBox Name="cmbFieldName" Width="150" Background="White" ItemsSource="{Binding}" SelectedItem="{Binding SelectedFieldName , Mode=TwoWay}" >
<ComboBox.ItemTemplateSelector>
<local:TreeViewSelector/>
</ComboBox.ItemTemplateSelector>
</ComboBox>
Here is the DataSet passed to this.
Datapoint _datapoint2 = new Datapoint();
_datapoint2.Name = "Alpha";
_datapoint2.FieldID.Add("Contains Elements");
_datapoint2.Field.Add("1 Year");
_datapoint2.Field.Add("2 Year");
_datapoint2.Field.Add("3 Year");
Try this:
private void TreeView_Loaded(object sender, RoutedEventArgs e)
{
TreeView areaTreeView = sender as TreeView;
// Expand the treeview
if (areaTreeView != null)
{
ExpandCollapseTreeNodes(areaTreeView, true);
}
}
public static void ExpandCollapseTreeNodes(ItemsControl parentContainer, bool isExpanded)
{
foreach (Object item in parentContainer.Items)
{
TreeViewItem currentContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (currentContainer != null) // && currentContainer.Items.Count > 0
{
//expand the item
currentContainer.IsExpanded = isExpanded;
//if the item's children are not generated, they must be expanded
if (isExpanded && currentContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
//store the event handler in a variable so we can remove it (in the handler itself)
EventHandler eh = null;
eh = new EventHandler(delegate
{
//once the children have been generated, expand those children's children then remove the event handler
if (currentContainer.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
ExpandCollapseTreeNodes(currentContainer, isExpanded);
currentContainer.ItemContainerGenerator.StatusChanged -= eh;
}
});
currentContainer.ItemContainerGenerator.StatusChanged += eh;
}
//otherwise the children have already been generated, so we can now expand those children
else
{
ExpandCollapseTreeNodes(currentContainer, isExpanded);
}
}
}
}
it is not pretty but works to select a node in the tree
// in code behind after the tree is declared, e.g. after InitializeComponent();
SynchronizationContext.Current.Post(delegate
{
// expand the tree to the selected node after loading
treeView.ExpandAll(); // or if path is known treeView.ExpandPath(...);
SynchronizationContext.Current.Post(delegate
{
treeView.GetContainerFromItem(root.Children[0]).IsSelected = true;
}, null);
}, null);

Accesing controls generated in ItemsControl through DataTemplate

Below is the markup that generates a list of buttons.
<ItemsControl x:Name="Items" Grid.Row="5" Grid.ColumnSpan="2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There is filtering applied to the buttons using a filtring criteria
collectionView = (CollectionView)CollectionViewSource.GetDefaultView(Items.ItemsSource);
collectionView .Filter = FilterList;
The problem is that i want to retain states of button checked when i toggle the filter state. I have tried subscribing to the event StatusChanged
Items.ItemContainerGenerator.StatusChanged += new System.EventHandler(ItemContainerGenerator_StatusChanged);
but the controls dont seem to be generated at the point when status is ContainersGenerated
void ItemContainerGenerator_StatusChanged(object sender, System.EventArgs e)
{
if (Items.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
RefreshButtons();
}
}
The only way to access them is to use the VisualTree.
Just use something like this:
public static T[] FindVisualChilds<T>(DependencyObject parent, Func<DependencyObject, bool> CompareDelegate)
where T : DependencyObject
{
if (VisualTreeHelper.GetChildrenCount(parent) == 0) return null;
List<T> childs = new List<T>();
if (CompareDelegate(parent))
childs.Add(parent as T);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var tmp = FindVisualChilds<T>(VisualTreeHelper.GetChild(parent, i), CompareDelegate);
if (tmp != null)
childs.AddRange(tmp);
}
return childs.ToArray();
}
Now pass as firstparameter your datagrid and as second a delegate that checks if the control is the control you want. As result you will get all control those are found

Categories