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;
}
Related
I have a grid inside a listview. I want that when the listview item is selected, the grid background is blue and for those that are unselected it is white.
XAML:
<ListView
x:Name="kategoriListView"
Margin="35,20,30,20"
VerticalAlignment="Stretch"
AutomationProperties.AutomationId="ItemsListView"
AutomationProperties.Name="Items"
ScrollViewer.VerticalScrollBarVisibility="Visible"
CornerRadius="15,15,15,15">
<ListView.ItemTemplate>
<DataTemplate>
<UserControl>
<Grid
x:Name="grid"
HorizontalAlignment="Stretch"
Height="50"
Background="White"
CornerRadius="15,15,15,15">
<TextBlock
x:Name="kelasText"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontSize="17"
FontWeight="Bold"
Foreground="Black"
Text="{Binding KategoriList}" />
</Grid>
</UserControl>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Resources>
<SolidColorBrush x:Key="ListViewItemBackgroundSelected" Color="Blue" />
<SolidColorBrush x:Key="ListViewItemBackgroundUnselected" Color "White" />
</ListView.Resources>
</Listview>
I tried it like the XAML above, but it didn't work and the Grid background is still white when the listview item is selected
The ListViewItemBackgroundSelected works for the container (the ListViewItem) but not the UserControl that you put in the DataTemplate. If you want to change the background color of the Gridinside the template, you will need to find the Grid using VisualTreeHelper Class.
So here are the steps:
Handle the SelectionChanged event of the kategoriListView
Find the selected Grid control using VisualTreeHelper
Change the background color of the selected Grid to blue.
Find the unselected Grid control
Change the background color of the selected Grid back to white
Here is the code that you could check.
Xaml:
<ListView
x:Name="kategoriListView"
Margin="35,20,30,20"
VerticalAlignment="Stretch"
SelectionChanged="kategoriListView_SelectionChanged"
AutomationProperties.AutomationId="ItemsListView"
AutomationProperties.Name="Items"
... more code
Code behind:
public DependencyObject FindChild(DependencyObject parant, string name)
{
int count = VisualTreeHelper.GetChildrenCount(parant);
for (int i = 0; i < count; i++)
{
var MyChild = VisualTreeHelper.GetChild(parant, i);
if (MyChild is FrameworkElement && ((FrameworkElement)MyChild).Name == name)
return MyChild;
var FindResult = FindChild(MyChild, name);
if (FindResult != null)
return FindResult;
}
return null;
}
private void kategoriListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// get selected listviewitem
ListViewItem item = kategoriListView.ContainerFromItem(kategoriListView.SelectedItem) as ListViewItem;
// the DependencyObject is the grid that you want to get
DependencyObject targetelement = FindChild(item, "grid");
Grid selectedText = targetelement as Grid;
selectedText.Background = new SolidColorBrush(Colors.Blue);
if (e.RemovedItems.Count > 0)
{
// change unselected item color
ListViewItem removedItem = kategoriListView.ContainerFromItem(e.RemovedItems.FirstOrDefault()) as ListViewItem;
DependencyObject removedelement = FindChild(removedItem, "grid");
Grid removedText = removedelement as Grid;
removedText.Background = new SolidColorBrush(Colors.White);
}
}
Result:
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
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;
}
I have a ListView that is enclosed inside a dragablz tabs plugin:
<dragablz:TabablzControl Opacity="0.8" BorderBrush="#FF4C589C" Margin="10,49,10,10" Background="#FF402D61" TabStripPlacement="Left">
<dragablz:TabablzControl.InterTabController>
<dragablz:InterTabController />
</dragablz:TabablzControl.InterTabController>
<TabItem Header="Texts">
<WrapPanel x:Name="textContainer"/>
</TabItem>
<TabItem Header="Files">
<ListView SelectionMode="Extended" x:Name="files" Background="#FF19174B" AllowDrop="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled" PreviewMouseLeftButtonDown="files_PreviewMouseLeftButtonDown" MouseMove="files_MouseMove">
<ListView.DataContext>
<local:FileItem/>
</ListView.DataContext>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel>
<local:FileItem/>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</TabItem>
</dragablz:TabablzControl>
The caller window calls InitItems() method of the window containing this ListView populating it with user controls of type FileItem:
public int InitItems()
{
FileOps.UserDataCollection userData = Utility.userData;
if (userData != null && userData.userData != null && userData.userData.Count > 0)
{
foreach (UserData ud in userData.userData)
{
switch (ud.DataType)
{
case "file": _FileItems.Add(new FileItem(ud));
break;
default: break;
}
}
files.ItemsSource = FileItems;
}
return 0;
}
FileItems and _FileItems are defined as:
private ObservableCollection<FileItem> _FileItems = new ObservableCollection<FileItem>();
public ObservableCollection<FileItem> FileItems
{
get
{
return _FileItems;
}
}
Which are getting data successfully when checked using a breakpoint on line files.ItemsSource = FileItems; in InitItems() method.
But the problem is that the ListView is not showing the user controls. Please tell me what I am missing? I am just a beginner in WPF.
I have a Datatemplete for List-box Item in which I have a Grid with two columns using WPF. In the first column I want to put few customized controls(Buttons) dynamically using C# in code behind. I don't know how to start and from where should I start, can anybody please help me with some great inputs and examples. Any answer will be greatly appreciate.
Thanks in advance.
XAML code:
<ListBox x:Name="ListBoxItem"
Grid.Row="1"
SelectionMode="Extended"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
FocusVisualStyle="{x:Null}"
KeyboardNavigation.IsTabStop="False"
Background="DarkGray"
ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="ListContent"
IsItemsHost="True"
Width="500"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="True"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel DockPanel.Dock="Left"
Width="30"
Height="{Binding Height}">
<--Here I want to put few customize buttons in code behind-->
</StackPanel>
<Image x:Name="MainPage"
Stretch="UniformToFill"
Source="{Binding ImagePath}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
You specified wanting to use code behind, so it would look something like this:
XAML:
<StackPanel Initialized="StackPanel_Initialized" .. />
Code behind:
using MyNamespace;
private void StackPanel_Initialized(object sender, EventArgs e)
{
MyControl newItem = new MyControl();
// Set any other properties
StackPanel parent = sender as StackPanel;
parent.Children.Add(newItem);
}
If you are looking for adding Controls inside a the First column of your grid then put a Panel inside the first column and in code behind add controls as child to that Panel. So as you mentioned in above that you are using DataTemplete then I would like to say that you can access that Panel something like:
Put the below codes inside the event where you wnt to add the controls.
ListBoxItem item = (ListBoxItem)(this.lst.ItemContainerGenerator.ContainerFromIndex(i));
ContentPresenter presenter = FindVisualChild<ContentPresenter>(item);
DataTemplate template = presenter.ContentTemplate;
StackPanel stack = (StackPanel)template.FindName("FirstColumn Panel Name", presenter);
and then call the below method:
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;
}