Hit testing bound item in ItemsControl? - wpf

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

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 dynamically generated textblock WPF MVVM Light

I am using MVVMM Light WPF and I want to do the following: Generate textboxes dynamically and bind them to a property of a class.
I already have the following but it doesn't show up in my view when running the application.
This is my collection:
private ObservableCollection<Border> _controllekes;
public ObservableCollection<Border> Controllekes
{
get { return _controllekes; }
set
{
_controllekes = value;
RaisePropertyChanged("Controllekes");
}
}
This it my xaml:
<ItemsControl ItemsSource="{Binding Path=Controllekes}">
</ItemsControl>
This is a part where I fill the itemsource "Controllekes":
Controllekes = new ObservableCollection<Border>();
Border border = new Border();
border.BorderThickness = new System.Windows.Thickness(5);
border.BorderBrush = Brushes.AliceBlue;
border.Padding = new System.Windows.Thickness(5);
TextBlock tb = new TextBlock();
tb.Background = Brushes.Red;
Binding nameTextBinding = new Binding("Controllekes");
nameTextBinding.Path = new System.Windows.PropertyPath(this.Dossier.Omschrijving);
nameTextBinding.Mode = BindingMode.OneWay;
//nameTextBinding.Source = this.Dossier.Omschrijving;
tb.SetBinding(TextBlock.TextProperty, nameTextBinding);
border.Child = tb;
this.Controllekes.Add(border);
What it does it creates a border with in this border a textblock where the binding should happen. I whish to bind the property this.Dossier.Omschrijving (Dossier is the class). If I just enter a string in the textbox it works.
In runtime the border gets generated but the textblock remains empty. The object Dossier.Omschrijving contains information.
What do I do wrong?
EDIT:
safe put me in the right direction and the answer of ItemsControl with multiple DataTemplates for a viewmodel made me finish the job :)
through all that and use ItemTemplate
<ItemsControl x:Name="ic">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding yourboolProperty}"/>
<TextBlock Background="Red" Text="{Binding yourStringProperty}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and set ic's ItemsSource to List

Find Control Inside ListBox?

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

Why Does ItemsControl Not Use My ItemTemplate?

I am able to use an ItemTemplate within an ItemsControl to render items in a specific format. However, if one of the items within the ItemsControl happens to be, say, a TextBox, that TextBox is rendered rather than an instance of the ItemsTemplate. From what I can tell, this is true for any FrameworkElement. Is this intended behavior for an ItemsControl, or am I doing something incorrectly?
An example:
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Rectangle Fill="Blue" Height="20" Width="20" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<sys:Object />
<TextBox />
<sys:Object />
<Rectangle Fill="Red" Height="20" Width="20" />
</ItemsControl.Items>
</ItemsControl>
I expected this to display four blue rectangles. I thought that any time an ItemTemplate has been defined each item in the collection is rendered as an instance of the template. However, in this case the following is rendered: a blue rectangle followed by a TextBox followed by a blue rectangle followed by a red rectangle.
The ItemsControl has a protected member IsItemItsOwnContainerOverride which is passed an object from the items collection and returns true if that object can be added directly to the items panel without a generated container (and thereby be templated).
The base implementation returns true for any object that derives from UIElement.
To get the behaviour you would expect you would need to inherit from ItemsControl and override this method and have it always return false. Unfortunately thats not the end of the matter. The default implementation of PrepareContainerForItemOverride still doesn't assign the ItemTemplate to the container if the item is a UIElement so you need to override this method as well:-
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
((ContentPresenter)element).ContentTemplate = ItemTemplate;
}
I'm just speculating here, but I would bet that it's behavior that lives inside of the ItemContainerGenerator. I'd bet that the ItemContainerGenerator looks at an item, and if it's a UIElement it says, "cool, the item container's been generated, I'll just return it" and if it's not, it says, "I'd better generate a container for this item. Where's the DataTemplate?"

Silverlight 4: how to display list of custom controls (not in list order)

There are following object:
'FieldItem' custom control;
'Field' - ... XAML-object, which will contains a dozen of field items;
FieldItemViewModel - data class that hosts data to be displayed with 'FieldItem' custom control;
position of 'FieldItem' control depend from data entity parameters that is bounded to the control (X and Y);
items - ObservableCollection - collection that contains a data.
Question: what kind of object should I put inside of the in order to have each item of the my FieldItems to be displayed inside of Canvas?
I've planned to use ListView... but... can't imagine how is it possible to change position of the list view item...
Any thoughts are welcome!
Thanks.
You can have a simple ItemsControl.
ItemsControl is just a container of items.
The ItemsPanel should be set to your canvas. And the data template of each item should be the 'FieldItem' control.
In your viewmodel expose a property that is called Items which will be a collection of the items data.
Something similar to this:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<FieldItem />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Silverlight doesn't have ItemContainerStyle but you can set it in code:
public class MyItemsControl : ItemsControl
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("Position.X");
Binding topBinding = new Binding("Position.Y");
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
}
Taken from here: http://forums.silverlight.net/forums/p/29753/96429.aspx

Resources