Find Control Inside ListBox? - wpf

<Style TargetType="ListBoxItem" x:Key="ListBoxItemTemplate">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Button Content="{TemplateBinding Content}"></Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListBox ItemsSource="{Binding S}"
x:Name="listBox"
ItemContainerStyle="{StaticResource ListBoxItemTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="grid" Columns="5"></UniformGrid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I want to Find "grid" from ListBox Control.Please Help Me,Thank you.

A couple of things to add to Meleak's answer (and this was a bit too long to put in a comment.)
Normally, the way you obtain a named element from a template in WPF is to call the template's FindName method. However, because templates are basically factories, you also needs to say which particular instance of the template you require - a single ItemsPanelTemplate may have been instantiated several times over. So you'd need something like this:
var grid = (UniformGrid) listBox.ItemsPanel.FindName("grid", ???);
But what goes in that ??? placeholder? It's not the ListBox itself - the ListBox doesn't actually use that ItemsPanel directly. Ultimately, the it's used by the ItemsPresenter in the ListBox's template. So you'd need to do this:
var grid = (UniformGrid) listBox.ItemsPanel.FindName("grid", myItemsPresenter);
...except, there's no reliable way to get hold of the ItemsPresenter either. In fact, there might not even be one - it's legal to create a template for a ListBox that just provides the hosting panel directly - there's even a special property, Panel.IsItemsHost, for this very purpose.
And that leads onto the second point I wanted to add. In scenarios where the ListBox's template doesn't use the ItemsPresenter, the ItemsPanel will go unused. So it's actually possible that the UniformGrid you're trying to get hold of doesn't even exist.

One way to do it is to store it in code behind once it is Loaded.
<ListBox ItemsSource="{Binding S}"
x:Name="listBox"
ItemContainerStyle="{StaticResource ListBoxItemTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="grid" Columns="5" Loaded="grid_Loaded"></UniformGrid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
And in code behind
private UniformGrid m_uniformGrid = null;
private void grid_Loaded(object sender, RoutedEventArgs e)
{
m_uniformGrid = sender as UniformGrid;
}
If you want to find it from the ListBox then you can use the Visual Tree.
UniformGrid uniformGrid = GetVisualChild<UniformGrid>(listBox);
public static T GetVisualChild<T>(object parent) where T : Visual
{
DependencyObject dependencyObject = parent as DependencyObject;
return InternalGetVisualChild<T>(dependencyObject);
}
private static T InternalGetVisualChild<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;
}

Related

selected item from listbox XAML

I am trying to get the selected item from the ListBox using listbox_SelectionChanged() method, but it does not seem to work. Could you tell me what is the best way to get the selected item out of listbox. the code I tried is bellow.
your help much appreciated.
XAML
<ListBox
x:Name="lbSkills"
Grid.Row="1"
Margin="10,0,10,10" SelectionChanged="LbSkills_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="Beige">
<Grid Width="auto" HorizontalAlignment="Stretch">
<TextBlock VerticalAlignment="Center" FontSize="26" Grid.Column="0" Foreground="Black" Text="{Binding SkillDescription}"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
XAML.cs - I have also tried commented code, but unable to get the selected item
private async void LbSkills_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//var addedItems = e.AddedItems;
//string selectedSkillString = "None";
//if (addedItems.Count > 0)
//{
// var selectedSkill = addedItems[0];
// selectedSkillString = selectedSkill.ToString();
//}
//lbSkills.SelectedItem.ToString();
MessageDialog msgBox = new MessageDialog(e.AddedItems.ToString());
await msgBox.ShowAsync();
}
First of all check what is the DataConntext or ItemsSource of you ListBox (it have to be an ObservableCollection to avoid the memory leaks).
Check if there is a Binding errors in the Output window.
Check if there is a correcct property to bind to.
Try the solution the next solution:
As I can understand you, the problem is that the added items of event argument doesn't contains the current selected item. But there is no any problem with your code. It returns the actual model (Skill) when I used it. But if you apply ToString() metod on it, you won't get the real model, the result will be just the full name of a class (<Full.Assembly.Path>.<Class_Name>). If you want to get the model instance you have to cast or safely cast the e.AddedItems content or you have to override the ToString() method in your model class. From another hand if you want to get the ListBoxItem itself for some reason try to use the next code:
var listBox = sender as ListBox;
var selected = e.AddedItems.Cast<object>().FirstOrDefault();
var container = listBox.ItemContainerGenerator.ContainerFromItem(selected);
regards

Binding on dynamically-added elements

TPTB have decided that our app must run in a single window, popping up new windows in modal mode is not allowed.
And naturally, we have a UI design that involves popping up modal dialogs all over the place.
So I added a top-level Grid to the Window. In that Grid I defined no rows or columns, so everything draws in Row 0/Column 0.
The first element in the Grid was another Grid that contained everything that was normally displayed in the Window. The second was a full-sized Border with a gray, semi-transparent Background. The rest were Borders with wide Margins and white Backgrounds, containing the various UserControls that needed to be displayed as popups. All but the first had Visibility="Collapsed".
And then, when I needed to show a popup, I'd set Visibility="Visible" on the gray background and on the appropriate UserControl. The result was a nice shadowbox effect that worked fine.
Until somebody decided that the popups needed to be able to display popups. In a non-predictable order.
The limitation of the method I had implemented, using Visibility="Collapsed" elements in a Grid was that their order was fixed. UserControlB would always be displayed on top of UserControlA, even if it was UserControlB that asked to have UserControlA displayed. And that's not acceptable.
So my next attempt was to define the various UserControls in Window.Resources, and to add them to the Grid in code:
this.masterGrid.Children.Add(this.Resources["userControlA"] as UserControlA);
And that almost works. But the bindings are all messed up.
As an example, one of the controls is supposed to bind a Property to the CurrentItem of a collection in a member object of the Window's viewmodel. When I had the control defined as an invisible item in the Grid, it worked fine. But when I defined it as a Resource, the Property was null - it was never bound.
So I tried binding it in code, after I added it to the grid:
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
And that compiles and runs just fine, but I'm not binding to the right object.
The first time I display the UserControl, I see the right object bound to it. But when I close it, and move the CurrentItem in the collection to a different object, and display the UserControl again, I still see the first object bound. If I close it again, and open it a third time, then I will see the right object bound to the control.
I've checked in code, and the CurrentItem that I'm binding to is right, every time, but it only seems to take every other time.
So I tried explicitly clearing the binding, first:
BindingOperations.ClearBinding(userControlA, UserControlA.myProperty);
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
But that doesn't seem to have made any difference.
In all, it feels like I'm running down a rabbit hole, chasing deeper and deeper into complexity, to solve what should be a fairly simple problem.
Does anyone have any suggestions as to:
How to get binding to work on dynamically-added elements, or
How to get arbitrarily-ordered popups to display, as shadowboxes, without using dynamically-ordered elements?
Thanks in advance.
While it seems really odd for me that you can't create new Windows, I would definitely recommend not to complicate it too much by doing unnecesary things such as storing your views in the MainWindow's resources.
It would be better if you just added new instances of these elements into an ObservableCollection:
XAML:
<Window x:Class="WpfApplication4.Window8"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window8" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<StackPanel Background="Green">
<TextBlock Text="This is ViewModel1!!"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<StackPanel Background="Blue" HorizontalAlignment="Center">
<TextBlock Text="This is ViewModel2!!"/>
<TextBlock Text="{Binding Text2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<StackPanel Background="Red" VerticalAlignment="Center">
<TextBlock Text="This is ViewModel3!!"/>
<TextBlock Text="{Binding Text3}"/>
<TextBox Text="{Binding Text3}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Button Width="100" Content="Add" Click="Add_Click" DockPanel.Dock="Top"/>
<Button Width="100" Content="Remove" Click="Remove_Click" DockPanel.Dock="Top"/>
<ListBox ItemsSource="{Binding ActiveWidgets}" SelectedItem="{Binding SelectedWidget}">
<ListBox.Template>
<ControlTemplate>
<ItemsPresenter/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter ContentSource="Content"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DockPanel>
</Window>
Code Behind:
using System.Linq;
using System.Windows;
using System.Collections.ObjectModel;
using System;
namespace WpfApplication4
{
public partial class Window8 : Window
{
private WidgetsViewModel Widgets { get; set; }
public Window8()
{
InitializeComponent();
DataContext = Widgets = new WidgetsViewModel();
}
private Random rnd = new Random();
private int lastrandom;
private void Add_Click(object sender, RoutedEventArgs e)
{
var random = rnd.Next(1, 4);
while (random == lastrandom)
{
random = rnd.Next(1, 4);
}
lastrandom = random;
switch (random)
{
case 1:
Widgets.ActiveWidgets.Add(new ViewModel1() {Text = "This is a Text"});
break;
case 2:
Widgets.ActiveWidgets.Add(new ViewModel2() { Text2 = "This is another Text" });
break;
case 3:
Widgets.ActiveWidgets.Add(new ViewModel3() { Text3 = "This is yet another Text" });
break;
}
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
private void Remove_Click(object sender, RoutedEventArgs e)
{
Widgets.ActiveWidgets.Remove(Widgets.SelectedWidget);
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
}
public class WidgetsViewModel: ViewModelBase
{
public ObservableCollection<ViewModelBase> ActiveWidgets { get; set; }
private ViewModelBase _selectedWidget;
public ViewModelBase SelectedWidget
{
get { return _selectedWidget; }
set
{
_selectedWidget = value;
NotifyPropertyChange(() => SelectedWidget);
}
}
public WidgetsViewModel()
{
ActiveWidgets = new ObservableCollection<ViewModelBase>();
}
}
public class ViewModel1: ViewModelBase
{
public string Text { get; set; }
}
public class ViewModel2: ViewModelBase
{
public string Text2 { get; set; }
}
public class ViewModel3: ViewModelBase
{
public string Text3 { get; set; }
}
}
Just copy and paste my code in a File - New - WPF Application and see the results for yourself.
Since the Grid always places the last UI Element added to it topmost, you will see that Adding items to the observablecollection makes these "different widgets" always appear on top of each other, with the topmost being the last one added.
The bottom line is, when WidgetA requests to open WidgetB, just create a new WidgetBViewModel and add it to the ActiveWidgets collection. Then, when WidgetB is no longer needed, just remove it.
Then, it's just a matter of putting your UserControls inside a proper DataTemplate for each ViewModel. I strongly suggest you keep a separate ViewModel for each of your Widgets, and if you need to share data between them, just share data between the ViewModels.
Don't attempt to do things like ListBox ItemsSource="{Binding Whatever, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}" unless you have a good reason to.
This way you no longer have to deal with Panel.ZIndex stuff. Maybe you can create a couple of attached properties to deal with things like focus and whatnot, but this approach is dead simple, and by far more performant than the Visibility and the Resources approaches.

Hit testing bound item in ItemsControl?

I have an ItemsControl bound to a list, MyItems, with objects of SomeType. When I click on my UI elements (i.e. ellipses), I want to get hold of the SomeType object.
This does NOT work:
public HitTestResultBehavior SomeTypeHitCallback(HitTestResult result)
{
if (result.VisualHit is Ellipse)
{
var ellipse = result.VisualHit as Ellipse;
// Does not work...
object item = itemsSource.ItemContainerGenerator.ItemFromContainer(ellipse);
// item now equals DependencyProperty.UnsetValue
// Here I want to change the property of the object
// associated with the Ellipse...
var o = item as SomeType;
o.IsSelected = !o.IsSelected;
return HitTestResultBehavior.Continue;
}
return HitTestResultBehavior.Stop;
}
private void Canvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var pt = e.GetPosition((UIElement)sender);
VisualTreeHelper.HitTest(
(UIElement)sender,
null,
new HitTestResultCallback(SomeTypeHitCallback),
new PointHitTestParameters(pt));
}
Here's the XAML:
<ItemsControl x:Name="itemsSource" ItemsSource="{Binding Path=MyItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas ClipToBounds="True" PreviewMouseLeftButtonDown="Canvas_PreviewMouseLeftButtonDown" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse x:Name="item" Width="{Binding Width}" Height="{Binding Height}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How can I find the SomeType object?
itemsSource.ItemContainerGenerator.ItemFromContainer will only work if you pass Item Container, but not visual elements of it. So you need to find ContentPresenter that contains the Ellipse, and pass that as argument to ItemFromContainer method. Since ItemsContainer for ItemsControl is ContentPresenter.
One way I see, is to go up by parents from VisualHit, until you find ContentPresenter, and call ItemFromContainer for that item. Try this, it should work. But the problem here might be that ContentPresenter may exist inside the template of ItemsContainer, and you will get null again. Definitely by chaning ItemsControl to ListBox will make easier to find ListBoxItem, but you will have to re-style it and remove additional features you don't require.
Also try to check Ellipse.DataContext, I could be exactly what you want

Pseudo-Infinite Grid

I've got a bit of a design issue here.
I've got a view:
<ItemsControl x:Name="CellVMs">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row"
Value="{Binding Position.Y}" />
<Setter Property="Grid.Column"
Value="{Binding Position.X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
which is bound to a collection of viewmodels, that have a position property which the style there uses to position it on the ItemPanelTemplate. (Only one viewmodel per cell, and the grid cells are fixed size)
1) I would like that Grid to be pseudo-infinite, ie as EditorVMs get added and subtracted, the Grid should dynamically add and delete Row\Col Definitions, and there should always be enough Grid, and there should always be enough to fill the parent view.
2) In my containing viewmodel, I import an IGridEditor implementation instance which has a Grid property. How can I bind the ItemsPanelTemplateGrid to the IEditor.Grid?
Right now, I add the CellVM's to a collection in the IGridEditor's methods, then when the containing vm imports the instance, sets the containing vm's CellVM collection to the instances collection, and the item control binds to that using Caliburn.Micro's conventions.
I'm using Caliburn.Micro\MEF btw.
Can anyone help me figure this out?
Edit:
Been trying to understand this, but I'm coming up empty.
Only thing I can find is
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid x:Name="EditorGrid"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
and in my viemodel:
[Import("EditorGrid", typeof(Grid))]
public Grid EditorGrid { get; set; }
and a corresponding Export in class that has the methods to add things to the grid/
1) I would like that Grid to be pseudo-infinite, ie as EditorVMs get
added and subtracted, the Grid should dynamically add and delete
Row\Col Definitions, and there should always be enough Grid, and there
should always be enough to fill the parent view.
You could create a custom Grid that automatically adds rows or columns as needed:
public class AutoExpandGrid : Grid
{
protected override System.Windows.Media.Visual GetVisualChild(int index)
{
var visualChild = base.GetVisualChild(index);
var uiElement = visualChild as UIElement;
if (uiElement != null)
EnsureEnoughRowsAndColumns(uiElement);
return visualChild;
}
private void EnsureEnoughRowsAndColumns(UIElement child)
{
int minRows = GetRow(child) + GetRowSpan(child);
int minColumns = GetColumn(child) + GetColumnSpan(child);
while (minRows > RowDefinitions.Count)
{
RowDefinitions.Add(new RowDefinition());
}
while (minColumns > ColumnDefinitions.Count)
{
ColumnDefinitions.Add(new ColumnDefinition());
}
}
}
(not sure GetVisualChild is the best place to do this, but it's the best I could find)

Why does ItemContainerGenerator return null?

I have a ListBox, and I need to set its ControlTemplate to a Virtualizing WrapPanel which is a class that extends VirtualizingPanel, using a style that looks like this:
<Style TargetType="{x:Type ListBox}" x:Key="PhotoListBoxStyle">
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}" >
<s:VirtualizingVerticalWrapPanel>
</s:VirtualizingVerticalWrapPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, in the private method of Virtualizing WrapPanel below I try to access this.ItemContainerGenerator, but I get null value, any idea what's the problem ??
private void RealizeFirstItem()
{
IItemContainerGenerator generator = this.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(0);
using (generator.StartAt(pos, GeneratorDirection.Forward))
{
UIElement element = generator.GenerateNext() as UIElement;
generator.PrepareItemContainer(element);
this.AddInternalChild(element);
}
}
I think I had a similar problem and this helped:
var necessaryChidrenTouch = this.Children;
IItemContainerGenerator generator = this.ItemContainerGenerator;
... for some reason you have to "touch" the children collection in order for the ItemContainerGenerator to initialize properly.
For Windows 8.1 Metro apps, the ItemContainerGenerator was depricated and will return null. New Apis:
ItemsControl.ItemContainerGenerator.ItemFromContainer = ItemsControl.ItemFromContainer
ItemsControl.ItemContainerGenerator.ContainerFromItem = ItemsControl.ContainerFromItem
ItemsControl.ItemContainerGenerator.IndexFromContainer = ItemsControl.IndexFromContainer
ItemsControl.ItemContainerGenerator.ContainerFromIndex = ItemsControl.ContainerFromIndex
http://msdn.microsoft.com/en-us/library/windows/apps/dn376326.aspx
Falck is mostly correct. Actually, you need to reference the 'InternalChildren' of the virtualized stack panel. The decompiled code for this property is:
protected internal UIElementCollection InternalChildren
{
get
{
this.VerifyBoundState();
if (this.IsItemsHost)
{
this.EnsureGenerator();
}
else if (this._uiElementCollection == null)
{
this.EnsureEmptyChildren(this);
}
return this._uiElementCollection;
}
}
The 'EnsureGenerator' does the work of making sure that a generator is available. Very poor 'just in time' design, IMO.
Most probably this is a virtualization-related issue so ListBoxItem containers get generated only for currently visible items (e.g. https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9)
I'd suggest switching to ListView instead of ListBox - it inherits from ListBoxand it supports ScrollIntoView() method which you can utilize to control virtualization;
targetListView.ScrollIntoView(itemVM);
DoEvents();
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem;
(the example above also utilizes the DoEvents() static method explained in more detail here; WPF how to wait for binding update to occur before processing more code?)
There are a few other minor differences between the ListBox and ListView controls (What is The difference between ListBox and ListView) - which should not essentially affect your use case.
This is because you changed the Template of the Listbox, while u should have just changed the ItemsPanel:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<s:VirtualizingVerticalWrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

Resources