How do I access a control of a DataTemplate - c#

I have two different DataTemplates in an UserControl Resource. One DataTemplate contains a image control and the other DataTemplate a media element control. The DataType of each DataTemplate represents a ImageViewModel respectively a VideoViewModel. In my user control a have a grid which contains a ContentControl. The content property of the content control is bound to a property which represents the current view model that should be used.
The idea is to change the content of the grid depending on the current view model
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:ImageScreensaverViewModel}">
<Image Source="{Binding Image}" Stretch="Uniform"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:VideoScreensaverViewModel}">
<MediaElement x:Name="Player" Source="{Binding Video}" LoadedBehavior="Play" />
</DataTemplate>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="MediaCommands.Pause" Executed="PausePlayer" CanExecute="CanExecute"/>
<CommandBinding Command="MediaCommands.Play" Executed="PlayPlayer" CanExecute="CanExecute"/>
</UserControl.CommandBindings>
<Grid>
<ContentControl x:Name="ScreanSaverContent" Content="{Binding CurrentVm}"/>
</Grid>
This works great, but I need to access the MediaElement in code behind so that I can control the media player (Play, Stop, Pause)
I already tried the solution posted on hier without any success. I can access only the selected view model though the content property.

Try this piece of code to reach to a control inside ContentPresenter:
public static FrameworkElement GetControlByName(DependencyObject parent, string name)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < count; ++i)
{
var child = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;
if (child != null)
{
if (child.Name == name)
{
return child;
}
var descendantFromName = GetControlByName(child, name);
if (descendantFromName != null)
{
return descendantFromName;
}
}
}
return null;
}

Related

WPF - Elements inside DataTemplate property issue when no binding?

I have the following TabControl:
<TabControl ItemsSource="{Binding Tabs"}>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type vm:TabVM}">
<TextBox></TextBox>
<TextBox Text="{Binding SomeProperty}"></TextBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The unexpected behaviour is that first TextBox has Text property shared between all tabitems, while second TextBox effectively bind to ViewModel property.
My need is to make independent the first TextBox too, even without binding.
What can I do ?
** UPDATE **
After several tries I've decided to use the ikriv's TabContent.cs.
The only issue I've found with this is that calling the TabControl.Items.Refresh() (i.e. after removing a tabItem) cause the reset of the internal cache.
An unelegant but effective solution may be this:
public ContentManager(TabControl tabControl, Decorator border)
{
_tabControl = tabControl;
_border = border;
_tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
/* CUSTOM */
var view = CollectionViewSource.GetDefaultView(((TabControl)_tabControl).Items);
view.CollectionChanged += View_CollectionChanged;
}
/*
* This fix the internal cache content when calling items->Refresh() method
* */
private void View_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
/* Retrieve all tabitems cache and store to a temp list */
IList<ContentControl> cachedContents = new List<ContentControl>();
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
var cachedContent = TabContent.GetInternalCachedContent(tabItem);
cachedContents.Add(cachedContent);
}
/* rebuild the view */
_tabControl.Items.Refresh();
/* Retrieve all cached content and store to the tabitems */
int idx = 0;
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
TabContent.SetInternalCachedContent(tabItem, cachedContents[idx++]);
}
}
}
You should use data binding since the same ContentTemplate will be applied for all items in your ItemsSource. Only the binding will be refreshed when you switch tabs basically. The TextBox isn't re-created nor reset.
What can I do ?
You could work around this in the view by handling the SelectionChanged event of the TabControl and reset the TextBox control yourself:
private void tabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabControl tc = sender as TabControl;
ContentPresenter cp = tc.Template.FindName("PART_SelectedContentHost", tc) as ContentPresenter;
if(cp != null && VisualTreeHelper.GetChildrenCount(cp) > 0)
{
ContentPresenter cpp = VisualTreeHelper.GetChild(cp, 0) as ContentPresenter;
if(cpp != null)
{
TextBox textBox = cpp.FindName("txt") as TextBox;
if (textBox != null)
textBox.Text = string.Empty;
}
}
}
<TabControl x:Name="tabs" ItemsSource="{Binding Tabs}" SelectionChanged="tabs_SelectionChanged">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<StackPanel>
<TextBox x:Name="txt"></TextBox>
</StackPanel>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
If you want to persist the text in the TextBox when you switch tabs you could use the attached behaviour from the following article and set its IsCached property to true: https://www.codeproject.com/articles/460989/wpf-tabcontrol-turning-off-tab-virtualization
<TabControl ItemsSource="{Binding Items}" behaviors:TabContent.IsCached="True">
<!-- Make sure that you don't set the TabControl's ContentTemplate property but the custom one here-->
<behaviors:TabContent.Template>
<DataTemplate>
<StackPanel>
<TextBox />
</StackPanel>
</DataTemplate>
</behaviors:TabContent.Template>
</TabControl>
Yet another approach would be to modify the ControlTemplate of the TabControl to include a ListBox as suggested by 'gekka' in the following thread on the MSDN forums: https://social.msdn.microsoft.com/Forums/en-US/4b71a43a-26f5-4fef-8dc5-55409262298e/using-uielements-on-datatemplate?forum=wpf

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

Change GridView Item DataTemplate based on seperate ViewModel Property in WinRT

What I am attempting to do is have a collection of items shown in a GridView control and have the size of these items change based on a command executed by a separate button.
For example, having a row of buttons across the top reading “Small”, “Medium” and “Large” and having the items in the GridView respond to the relevant command by displaying its items in the relevant state.
I have the gridview declared like so
<GridView ItemsSource="{Binding Squares}"
With Squares being an observable collection of Square objects that have a Title and a Fill property.
At first I went down the DataTemplateSelector route by declaring the following data templates in the Resources section of the page.
<DataTemplate x:Key="SquareSmallTemplate">
<Grid Height="100" Width="100">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareMediumTemplate">
<Grid Height="150" Width="150">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareLargeTemplate">
<Grid Height="200" Width="200">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
The idea being that the grid’s height and width properties are different for the relevant template. I declared the following data templates in the selector
public DataTemplate SmallTemplate { get; set; }
public DataTemplate MediumTemplate { get; set; }
public DataTemplate LargeTemplate { get; set; }
And in the SelecteTemplateCore method I just returned the relevant template
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
string value = item as string;
if (value != null)
{
if (value == "Small")
return SmallTemplate;
else if (value == "Medium")
return MediumTemplate;
else if (value == "Large")
return LargeTemplate;
return base.SelectTemplate(item, container);
}
else
{
return base.SelectTemplateCore(item, container);
}
}
However, with this method (and, by design of the DataTemplateSelector) the object being passed in is the item in the collection (the Square).
This is fine if I wanted each item to have a different appearance or something, but what I need is the template to change based on another property on the view model.
For this, I have the following
public string State {get; set;}
and this is set to “Small”, “Medium, or “Large based on a separate row of three buttons that execute a command that sets this property to the relevant value.
How do I relate the State property to changing to the relevant DataTemplate?
Another route I tried was to have a single Data template that used the VSM to animate the Height/Width properties in the relevant states. However I could not get the relevant animation to execute when the State changed.
Any help would be great, thanks
There are a few ways to do this, I'm not sure which would be best. In any case, you'll need 1) a trigger, and 2) the action to update the template. I am leaning towards using PropertyChangedTrigger along with an InvokeCommandAction.
<GridView x:Name="grid">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding State}">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=UpdateTemplateCommand}" CommandParameter="{Binding State}" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<GridView>
(Here the "AncestorType" would just be the root of the view, so please change "UserControl" as needed.)
Then in the view, you would have an ICommand that updates the template:
UpdateTemplateCommand = new DelegateCommand(state => {
switch ((string)state)
{
default:
case "Small" : grid.ItemTemplate = "SquareSmallTemplate"; break;
case "Medium" : grid.ItemTemplate = "SquareMediumTemplate"; break;
case "Large" : grid.ItemTemplate = "SquareLargeTemplate"; break;
}
});
IDK ... after writing this out it seems a bit convoluted. Maybe you'd find it preferable to add a CurrentDataTemplate property to the view-model, and assign it by creating DataTemplates from strings using XamlReader.

LogicalTreeHelper.GetChildren - ObservableCollection Move() causing ContentControl in DataTemplate to lose it's content?

This is very strange. The point of the code below is to support an attachedProperty which will notify a container if any of it's children have received focus.
i.e. I have a Grid with a textBox somewhere in it's Content and I want to turn the Grid Blue if one of those controls gets focus.
I have a ListView with an ItemsTemplate. The ItemsTemplate is a DataTemplate containing a few things...but one of them being a ContentControl.
Example:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<ContentControl Content="{Binding Something}"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The Binding on the ContentControl should display a certain type of UserControl.
Upon creation...works just fine. If I recursively iterate template of the listViewItem starting with the Grid element...it will traverse the ContentControl's "Content" as well.
HOWEVER...once I do a .Move() on the ObservableCollection that the ListView ItemsSource is bound to, the ContentControl.Content is empty according to the LogicalTreeHelper.
What gives?
If I inspect the ContentControl, it SHOWS me the content...but LogicalTreeHelper.GetChildren returns and empty Enumerator.
I'm confused...
Can anyone explain why this would be the case?
LogicalTreeHelper iterator method
public static void applyFocusNotificationToChildren(DependencyObject parent)
{
var children = LogicalTreeHelper.GetChildren(parent);
foreach (var child in children)
{
var frameworkElement = child as FrameworkElement;
if (frameworkElement == null)
continue;
Type frameworkType = frameworkElement.GetType();
if (frameworkType == typeof(TextBox) || frameworkType == typeof(ListView) ||
frameworkType == typeof(ListBox) || frameworkType == typeof(ItemsControl) ||
frameworkType == typeof(ComboBox) || frameworkType == typeof(CheckBox))
{
frameworkElement.GotFocus -= frameworkElement_GotFocus;
frameworkElement.GotFocus += frameworkElement_GotFocus;
frameworkElement.LostFocus -= frameworkElement_LostFocus;
frameworkElement.LostFocus += frameworkElement_LostFocus;
// If the child's name is set for search
}
applyFocusNotificationToChildren(child as DependencyObject);
}
}
Aloha,
Here is an suggetion how you could solve your problem:
I am not sure if i spelled right the GotFocus event but its a RoutedEvent and you can use it anywhere in your visual tree.
If one of your items receives a focus your ListView will get notified and inside the handler you can do whatever you want.
How about this:
<ListView GotFocus="OnGotFocus">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<ContentControl Content="{Binding Something}"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is just some random logic to demonstrate what you can do.
public void OnGotFocus(object sender, RoutedEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if(((MyViewModel)item.Content).SomeColor == "Blue")
{
Grid g = VisualTreeHelper.GetChild(item, 0) as Grid;
g.Background = Colors.Blue;
}
}
GotFocus is a RoutedEvent which will bubble up the visual tree if fired. So catch the event somewhere and inspect which was the original source object that fired the event. Or inspect what was the ViewModel propery of the object which fired the event.

Access Page resource element

How can I access page resource element in C# coding? I have the following piece of code in my XAML. I want to access the image element in my C# Code, but it is not accessible.
<Page.Resources>
<DataTemplate x:Key="Standard250x250ItemTemplate">
<Grid HorizontalAlignment="Left" Width="150" Height="150">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image x:Name="image" Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" />
</Border>
</Grid>
</DataTemplate>
It is not accessible because a DataTemplate resource does not get instantiated until it is loaded. You would need to do something like this to load it first:
var dataTemplate = (DataTemplate)this.Resources["Standard250x250ItemTemplate"];
var grid = dataTemplate.LoadContent();
and then traverse the element tree to get to the Image.
A better approach in many scenarios is to define an attached dependency property or attached behavior that you can attach to your Image in XAML and write code related to the associated Image.
It depends on when you are trying to access it. If trying to access the image control of elements that have already been rendered then you can use ItemContainerGenerator like such:
//assumes using a ListView
var item = (ListViewItem)listView.ItemContainerGenerator.ContainerFromItem(myModel);
// traverse children
var image = GetChildOfType<Image>(item);
// use the image!
private T GetChildOfType<T>(DependencyObject obj)
{
for(int i = 0; i< VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if(child is T) return child as T;
T item = GetChildOfType<T>(child);
if(item != null) return item;
}
return null;
}
If you need to change properties of the image, then that can be accomplished through binding as well.

Categories

Resources