Binding in WPF not working as expected - c#

I have a MainWindow.xaml, MainwindowViewModel.cs, HaemogramReport.xaml and HaemogramReport.xaml.cs. I have other files as well in my project, but the problem lies in the above mentioned four files.
I am posting the minimal code here so that others can catch the problem.
Now in HaemogramReport.xaml I declare some controls like Grid, TextBox, TextBlock, Rectangle, Border, ContentControl etc.
For example HaemogramReport.xaml looks like:
<Page.DataContext>
<vm:MainWindowViewModel />
</Page.DataContext>
<Grid DataContext="{Binding Source={StaticResource Settings}}" PreviewMouseDown="Object_Selection" x:Name="Root">
<Border Style="{StaticResource BorderStyle}" x:Name="HaemogramTestBorder"
Grid.Row="{Binding Default.HaemogramTestGridRow}" Grid.Column="{Binding Default.HaemogramTestGridColumn}"
Grid.RowSpan="{Binding Default.HaemogramTestGridRowSpan}" Grid.ColumnSpan="{Binding Default.HaemogramTestGridColumnSpan}">
<Grid>
<Rectangle Fill="Transparent" x:Name="HaemogramTestRectangle"/>
<TextBlock x:Name="HaemogramTestTextBlock"
Text="{Binding Default.HaemogramTestText}" Visibility="{Binding Default.HaemogramTestVisibility}"
Background="{Binding Default.HaemogramTestBackground, Converter={StaticResource colorToSolidColorBrushConverter}}"
Foreground="{Binding Default.HaemogramTestForeground, Converter={StaticResource colorToSolidColorBrushConverter}}"
FontFamily="{Binding Default.HaemogramTestFontFamily, Converter={StaticResource stringToFontFamilyConverter}}"
FontSize="{Binding Default.HaemogramTestFontSize}"
FontWeight="{Binding Default.HaemogramTestFontWeight}" FontStyle="{Binding Default.HaemogramTestFontStyle}"
HorizontalAlignment="{Binding Default.HaemogramTestHorizontalAlignment}"
VerticalAlignment="{Binding Default.HaemogramTestVerticalAlignment}"
Margin="{Binding Default.HaemogramTestMargin}" />
</Grid>
</Border>
</Grid>
When I click on any of the element in the above declared elements, the mousedown event of the grid named Root is raised.
That event handler is in HaemogramReport.xmal.cs. Here it is:
private void Object_Selection(object sender, MouseButtonEventArgs e)
{
var mouseWasDownOn = e.Source as FrameworkElement;
if (mouseWasDownOn != null)
{
foreach (Border border in FindVisualChildren<Border>(Root))
{
border.BorderBrush = Brushes.Transparent;
}
if (!(mouseWasDownOn is Border))
{
FindParent<Border>(mouseWasDownOn).BorderBrush = Brushes.Orange;
}
MainWindowViewModel mwvm = new MainWindowViewModel();
mwvm.SelectedObj = mouseWasDownOn;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
In mouseDown handler of Grid named Root, I say mwvm.SelectedObj = mouseWasDownOn;
SelectedObj is a property of type FrameworkElement which is declared in MainwindowViewModel.cs as follows:
private FrameworkElement selectedObj;
public FrameworkElement SelectedObj
{
get
{
return selectedObj;
}
set
{
selectedObj = value;
OnPropertyChanged("SelectedObj");
}
}
Now in my MainWindow I have for example a grid and a textBox inside it. The problematic bindings are declared here. xaml looks like:
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid DataContext="{Binding SelectedObj, UpdateSourceTrigger=PropertyChanged}">
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, TargetNullValue='null', FallbackValue='Error'}"/>
</Grid>
When using the above code, I always get the Text Error in above TextBox.
At the first chance I thought that this might be the binding error, so I changed my MainWindowViewModel.cs as follows:
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
SelectedObj = txt;
}
TextBlock txt = new TextBlock()
{
Text = "123"
};
private FrameworkElement selectedObj;
public FrameworkElement SelectedObj
{
get
{
return selectedObj;
}
set
{
selectedObj = value;
OnPropertyChanged("SelectedObj");
}
}
}
After making the above changes when I run my project I can see 123 in textbox but when I click on any element the text in the textbox does not change.
Now the question here is that if its a binding error then why in second example I get 123 in textbox while in 1st example I get Error - the fallback value.
And if it's not a binding error then what is the problem in above code?
Update
When I debug, I found that get part of SelectedObj is never called. But I don't know why?
Update -- Reed Copsey
Here is my new class:
public class DesignMethods
{
public static void FindCurrentlyClickedElement(DependencyObject Root, MouseButtonEventArgs e, MainWindowViewModel vm)
{
var mouseWasDownOn = e.OriginalSource as FrameworkElement;
if (mouseWasDownOn != null)
{
foreach (Border border in FindVisualChildren<Border>(Root))
{
border.BorderBrush = Brushes.Transparent;
}
if (!(mouseWasDownOn is Border))
{
FindParent<Border>(mouseWasDownOn).BorderBrush = Brushes.Orange;
}
vm.SelectedObj = mouseWasDownOn;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
}
And I use it like:
private void Object_Selection(object sender, MouseButtonEventArgs e)
{
DesignMethods.FindCurrentlyClickedElement(Root, e, this.DataContext as MainWindowViewModel);
}

The problem is you're creating a new instance of the ViewModel, not using the existing one:
// This is not the same instance you're binding to!
// MainWindowViewModel mwvm = new MainWindowViewModel();
// Get the existing one instead
var mwvm = this.DataContext as MainWindowViewModel;
mwvm.SelectedObj = mouseWasDownOn;
Note that I would likely not use the term "ViewModel" here, though. What you are doing is very much not a typical MVVM scenario as you're tightly coupling your DataContext instance into your View, with coupling happening in both directions, which is pretty much the opposite of the normal goals of MVVM.
Edit:
You may also need to update your bindings for SelectedObj. I would recommend trying with the XAML set to:
<Grid>
<TextBox Text="{Binding SelectedObj.Text, UpdateSourceTrigger=PropertyChanged, TargetNullValue='null', FallbackValue='Error'}"/>
</Grid>

Try to use the OriginalSource instead of Source:
var mouseWasDownOn = e.OriginalSource as FrameworkElement;
because the Source property when dealing with composite controls, can be the parent that contains the OriginalSource object (in your case the grid).

I think your error might be that FrameworkElement doesn't have a Text property http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement(v=vs.110).aspx
EDIT: Try updating your binding on Text to be
{Binding SelectedObj.Text}

I think your mistake might be that you are using "common" properties instead of DependencyProperties.
As you can see on Microsoft Description
"When you define your own properties and want them to support many
aspects of Windows Presentation Foundation (WPF) functionality,
including styles, data binding, inheritance, animation, and default
values, you should implement them as a dependency property."
These are the correct types of property to fully use all resources provided by WPF
Take a look at these links
http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/ms750428(v=vs.110).aspx
Or simply look for Dependency Property WCF on google.
Another useful link to understand the difference between these properties is
https://stackoverflow.com/a/3674727
which I used when I had similar problems.
Hope it helps!

Related

How to bind and display ListBoxItem index in ListBox?

I have a .NET 5 project with following Nuget Packages:
HandyControls for UI
Gong-Wpf-DragDrop for Drag and Drop elements in a List
I have a XAML with a ListBoxand a ViewModel with aObservableCollection` of Model.
The ObservableCollection is binded as ItemSource of ListBox
What I want to achieve:
When i Drag and Drop an item in a different position (or Add/Delete), I want the indexes to be refreshed.
Example:
Before Drag/Drop
After Drag/Drop
Actually, i binded the drophandler of gong-wpf-dragdrop
and at the end of the drop, i manually refresh every single Index in my list.
there is a way to do it easily? because actually i have to refresh indexes manually.
Summarizing:
When i reorder/delete/add items i want Model.Index of every item updated with the correct index position in ListBox.
My Mandate is:
Show index (one based)
Give the possibility to reorder the elements
I tried looking for similar questions but didn't find much that could help me.
Thanks in advance :)
Model:
public class Model : BindablePropertyBase
{
private int index;
private string name;
public int Index
{
get { return index; }
set
{
index = value;
RaisePropertyChanged();
}
}
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged();
}
}
}
And below a xaml with a simple binded list
MainWindow.xaml
<hc:Window x:Class="ListBoxIndexTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dd="clr-namespace:GongSolutions.Wpf.DragDrop;assembly=GongSolutions.Wpf.DragDrop"
xmlns:local="clr-namespace:ListBoxIndexTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="600">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding TestList}" dd:DragDrop.DropHandler="{Binding}"
dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDragSource="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10"
Background="Aqua">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Index}" Margin="10,0"/>
<TextBlock Text="{Binding Name}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</hc:Window>
MainWindowViewModel.cs
public class MainWindowViewModel : BindablePropertyBase, IDropTarget
{
private ObservableCollection<Model> test_list;
public ObservableCollection<Model> TestList
{
get
{
return test_list;
}
set
{
test_list = value;
RaisePropertyChanged();
}
}
// Constructor
public MainWindowViewModel()
{
TestList = new ObservableCollection<Model>()
{
new Model()
{
Index = 1,
Name = "FirstModel"
},
new Model()
{
Index = 2,
Name = "SecondModel"
},
new Model()
{
Index = 3,
Name = "ThirdModel"
}
};
}
public void DragOver(IDropInfo dropInfo)
{
Model sourceItem = dropInfo.Data as Model;
Model targetItem = dropInfo.TargetItem as Model;
if (sourceItem != null && targetItem != null)
{
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
dropInfo.Effects = DragDropEffects.Move;
}
}
public void Drop(IDropInfo dropInfo)
{
Model sourceItem = dropInfo.Data as Model;
Model targetItem = dropInfo.TargetItem as Model;
if(sourceItem != null && targetItem != null)
{
int s_index = sourceItem.Index - 1;
int t_index = targetItem.Index - 1;
TestList.RemoveAt(s_index);
TestList.Insert(t_index, sourceItem);
RefreshAllIndexes();
}
}
private void RefreshAllIndexes()
{
for (int i = 0; i < TestList.Count; i++)
{
TestList[i].Index = i + 1;
}
}
}
I don't believe there is an out-of-the box way to bind to a container index in WPF. Your solution is actually easy to understand.
If you find yourself binding often to index, you could create your own attached property/value converter that internally climbs up the visual tree using these helpers until it finds the parent ItemsControland makes use of the IndexFromContainer method.
Here is some code to get you started with this method:
First a small helper function to climb up the visual tree looking for an item of generic type:
public static DependencyObject FindParentOfType<T>(DependencyObject child) where T : DependencyObject {
//We get the immediate parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) {
return null;
}
//check if the parent matches the type we're looking for
if (parentObject is T parent) {
return parent;
} else {
return FindParentOfType<T>(parentObject);
}
}
Then a value converter that takes a control as input value and returns its index in the first encountered ItemsControl:
public class ContainerToIndexConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
//Cast the passed value as an ItemsControl container
DependencyObject container = value as ContentPresenter;
if (container == null) {
container = value as ContentControl;
}
//Finds the parent ItemsControl by looking up the visual tree
var itemControls = (ItemsControl)FindParentOfType<ItemsControl>(container);
//Gets the index of the container from the parent ItemsControl
return itemControls.ItemContainerGenerator.IndexFromContainer(container);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
And this is how you would use it in XAML:
<ListBox.ItemTemplate>
<DataTemplate>
<!-- This will display the index of the list item. -->
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentPresenter}, Converter={StaticResource ContainerToIndexConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>

FindVisualChild - Get properties of named UI Elements in ItemsControl

I generate UI elements at runtime by using a ItemsControl. The UI generates successfully but if I am unable to get any properties of the generated UI items, such as "Content" for a label, or SelectedItem for a ComboBox. I tried to get these properties by using this tutorial , and these answers but I always get a NullReferenceException.
The ItemsControl in XAML looks like this:
<ItemsControl Name="ListOfVideos">
<ItemsControl.Background>
<SolidColorBrush Color="Black" Opacity="0"/>
</ItemsControl.Background>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" Height="100" Width="175" x:Name="VideoThumbnailImage" Stretch="Fill" Source="{Binding VideoThumbnailURL}" Grid.Column="0"></Image>
<Label x:Name="VideoTitleLabel" Content="{Binding VideoTitleText}" Foreground="White" Grid.Column="1" VerticalAlignment="Top" FontSize="16" FontWeight="Bold"></Label>
<Label x:Name="VideoFileSizeLabel" Content="{Binding VideoTotalSizeText}" Foreground="White" FontSize="14" Grid.Column="1" Margin="0,0,0,35" VerticalAlignment="Bottom"></Label>
<Label x:Name="VideoProgressLabel" Content="{Binding VideoStatusText}" Foreground="White" FontSize="14" Grid.Column="1" VerticalAlignment="Bottom"></Label>
<ComboBox x:Name="VideoComboBox" SelectionChanged="VideoComboBox_SelectionChanged" Grid.Column="2" Width="147.731" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,50" ItemsSource="{Binding VideoQualitiesList}"></ComboBox>
<Label Content="Video Quality" Foreground="White" FontSize="14" VerticalAlignment="Top" Grid.Column="2" HorizontalAlignment="Center"></Label>
<Label Content="Audio Quality" Foreground="White" FontSize="14" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,27" Grid.Column="2"></Label>
<Slider x:Name="VideoAudioSlider" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="147.731" Maximum="{Binding AudioCount}"></Slider>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is how I generate the UI elements
public class VideoMetadataDisplay
{
public string VideoTitleText { get; set; }
public int AudioCount { get; set; }
public string VideoThumbnailURL { get; set; }
public string VideoStatusText { get; set; }
public string VideoTotalSizeText { get; set; }
public List<string> VideoQualitiesList { get; set; }
}
public partial class PlaylistPage : Page
{
private void GetPlaylistMetadata()
{
List<VideoMetadataDisplay> newList = new List<VideoMetadataDisplay>();
//populate the list
ListOfVideos.ItemsSource = newList;
}
}
And this is how I'm trying to get the properties of the UI elements
public class Utils
{
public 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;
}
}
private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UIElement CurrentItem = (UIElement)ListOfVideos.ItemContainerGenerator.ContainerFromItem(ListOfVideos.Items.CurrentItem);
Utils utils = new Utils();
ContentPresenter CurrentContentPresenter = utils.FindVisualChild<ContentPresenter>(CurrentItem);
DataTemplate CurrentDataTemplate = CurrentContentPresenter.ContentTemplate;
Label VideoTitle = (Label)CurrentDataTemplate.FindName("VideoTitleLabel", CurrentContentPresenter);
string VideoTitleText = VideoTitle.Content.ToString();
MessageBox.Show(VideoTitleText);
}
Every time I try to run this, FindVisualChild always returns one of the labels (VideoTitleLabel) instead of returning the ContentPresenter for the currently active item. CurrentDataTemplate is then null and I am unable to get any of the UI elements from it.
It is impossible that FindVisualChild<ContentPresenter> returns a Label instance. FindVisualChild casts the result to ContentPresenter. Since Label is not a ContentPresenter, this would throw an InvalidCastException. But before this, child is childItem would return false in case child is of type Label and the generic parameter type childItem is of type ContentPresenter and therefore a potential null is returned.
Short Version
Accessing the the DataTemplate or looking up controls just to get their bound data is always too complicated. It's always easier to access the data source directly.
ItemsControl.SelectedItem will return the data model for the selected item. You are usually not interested in the containers.
private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
var item = listView.SelectedItem as VideoMetadataDisplay;
MessageBox.Show(item.VideoTitleText);
}
Your Version (FindVisualChild improved)
FindVisualChild is weakly implemented. It will fail and throw an exception if traversal encounters a child node without children i.e. the parameter obj is null. You have to check the parameter obj for null before invoking VisualTreeHelper.GetChildrenCount(obj), to avoid a null reference.
Also you don't need to search the element by accessing the template. You can look it up directly in the visual tree.
I have modified your FindVisualChild method to search elements by name. I also have turned it into an extension method for convenience:
Extension Method
public static class Utils
{
public static bool TryFindVisualChildByName<TChild>(
this DependencyObject parent,
string childElementName,
out TChild childElement,
bool isCaseSensitive = false)
where TChild : FrameworkElement
{
childElement = null;
// Popup.Child content is not part of the visual tree.
// To prevent traversal from breaking when parent is a Popup,
// we need to explicitly extract the content.
if (parent is Popup popup)
{
parent = popup.Child;
}
if (parent == null)
{
return false;
}
var stringComparison = isCaseSensitive
? StringComparison.Ordinal
: StringComparison.OrdinalIgnoreCase;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is TChild resultElement
&& resultElement.Name.Equals(childElementName, stringComparison))
{
childElement = resultElement;
return true;
}
if (child.TryFindVisualChildByName(childElementName, out childElement))
{
return true;
}
}
return false;
}
}
Example
private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
object item = listView.SelectedItem;
var itemContainer = listView.ItemContainerGenerator.ContainerFromItem(item) as ListViewItem;
if (itemContainer.TryFindVisualChildByName("VideoTitleLabel", out Label label))
{
var videoTitleText = label.Content as string;
MessageBox.Show(videoTitleText);
}
}

Access Datatemplate with name

I've got many TabItems, but only one DataGrid in a DataTemplate.
Now I will access the DataGrid, because I want to change the content...
<Window.Resources>
<DataTemplate x:Key="ContentTabItem">
<DataGrid Grid.Row="1" Name="_uiDataGrid"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl Name="_uiTabControl" SelectionChanged="_uiTabControl_SelectionChanged">
<TabItem Name="_uiTabItembla1" Header="bla1" ContentTemplate="{StaticResource ResourceKey=ContentTabItem}"/>
<TabItem Name="_uiTabItembla2" Header="bla2" ContentTemplate="{StaticResource ResourceKey=ContentTabItem}"/>
</TabControl>
</Grid>
I cant access with the name _uiDataGrid
Try something like this to get the datagrid from the content presenters content template :
var contentPresenter = FindVisualChild<ContentPresenter>(_uiTabControl);
var dt = contentPresenter.ContentTemplate;
var datagrid = DataTemplateName.FindName("_uiDataGrid", contentPresenter)
as DataGrid;
For finding the visual child, refer the functions FindVisualChildren() and FindVisualChild() as mentioned in this stackoverflow post.
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
foreach (childItem child in FindVisualChildren<childItem>(obj))
{
return child;
}
return null;
}

Retrieving the TabItem hosting a nested WPF Control

I have some nested controls on a wpf TabControl.
I simply need to find the specific TabItem which is the parent of the control. Preferably using LINQ. Any ideas.
A non-selected TabItem don't exist in the Visual tree, but in the Logical tree.
I've made a small example on this:
<TabControl x:Name="TabControl">
<TabItem Header="Test 1">
<Button Content="Click to get TabItem Name" Margin="100" Click="btnGetParent_Click"/>
</TabItem>
<TabItem Header="Test 2"/>
</TabControl>
Clicking on the Button inside the TabItem should display the TabItem's header meaning you have access to it and you would be able to do any work you need.
I've kept the code pretty simple here too:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnGetParent_Click(object sender, RoutedEventArgs e)
{
TabItem ti = TryFindParent<TabItem>(sender as Button);
MessageBox.Show(ti.Header.ToString());
}
}
And this is the helper method:
public T TryFindParent<T>(DependencyObject child)
where T : DependencyObject
{
DependencyObject parentObject = LogicalTreeHelper.GetParent(child);
if (parentObject == null) return null;
T parent = parentObject as T;
if (parent != null)
return parent;
else
return TryFindParent<T>(parentObject);
}
The call to LogicalTreeHelper instead of the usual VisualTreeHelper does the job here.
Give it a try, i hope this helps.

Accessing Controls in DataTemplate in ListBox in WPF

I have listbox which have DataTemplate.
I can not access controls which placed in the datatemplate.
How can I access to this controls?
<ListBox Height="344" Name="listBoxMedicine" Width="881">
<ListBox.ItemTemplate>
<DataTemplate >
<TextBlock Name="myTextBlock">
</Datatemplate>
</ListBox.ItemTemplate>
</ListBox>
Thank you for your attention.
If you still want access your controls in codebehaind, you can do something like this:
1) Add a new helper method somewhere:
public static IEnumerable<Visual> ToVisualTree(this Visual visual)
{
yield return visual;
int numVisuals = VisualTreeHelper.GetChildrenCount(visual);
for (int i = 0; i < numVisuals; ++i)
{
var child = (Visual)VisualTreeHelper.GetChild(visual, i);
if (child == null) yield break;
foreach (var subItem in child.ToVisualTree())
{
yield return subItem;
}
}
}
2) Use it like this:
var allTextBlocks = listBoxMedicine.ToVisualTree().OfType<TextBlock>().ToList();
But I still strongly recomend to refactor your data model.
Based on the comments i would suggest you create a view-model which simply provides a property for the visbility, e.g.:
public class DataViewModel : INotifyPropertyChanged
{
private Data _data;
// Some data property.
public Data Data { get { return _data; } set { ... } }
private Visibility _visibility;
// The visibility property.
public Visibility Visibility { get { return _visibility; } set { ... } }
}
You can then bind that visibility and later set it in code to affect the view:
<DataTemplate >
<TextBlock Name="myTextBlock" Visibility="{Binding Visibility}">
</Datatemplate>
I'm using this approach to get FrameworkElement from ItemsControl, also will work with ListBox, ListView because they all inherit from ItemsControl.
private void CheckBounds(ItemsControl itemsControl)
{
foreach (var item in itemsControl.Items)
{
var child = ((FrameworkElement)itemsControl.ItemContainerGenerator.ContainerFromItem(item));
child.IsEnabled = child.IsControlVisible(itemsControl);
}
}

Categories